Building an Events App with Meteor and React

Joy Warugu
👁️ 5,761 views
💬 comments

Meteor is an open source, fullstack javascript platform. This allows you to develop in one language. It has some really cool perks the best of which is;

  • It provides complete reactivity; this means your UI reflects the actual state of the world with little development effort
  • The server sends actual data not HTML, and then the client renders it

Meteor supported Blaze for its view layers but after Meteor 1.2 it supported other JS frameworks i.e Angular and React.

In this article, I'll be showing off how Meteor and React play together. The point of this article is to get you interested in trying out these two technologies together and build something worthwhile in the future using them.

Table of Contents

    We will be creating an Events App, that encompasses the basic CRUD (Create, Read, Update, Delete) functions.

    Pre-requisites

    We require to have meteor running on our machines.

    Windows: Install chocolatey and run the following command

    > choco install meteor

    OS X || Linux:

    > curl https://install.meteor.com/ | sh

    Some basic knowledge on meteor and react will also go a long way. But have no fear, you can still follow along even if you are a newbie. I share some resources in the tutorial that will help ramp up.

    Setup

    Its really easy to setup Meteor projects. All we need to do is:

    > meteor create events-sync

    Voilà we get our project all set up. There are a couple of files in the folder. Let's do a quick run through to know what each is.

    client
        main.js         // entry point on the client side
        main.html      // HTML that defines our template files
        main.css      // where we define our styles
    server
        main.js      // entry point on the server side
    package.json        // npm packages are stored in here
    package-lock.json  // lock file for npm packages
    .meteor           // meteor files
    .gitignore       // git ignore stuff

    To run the project we'll run these commands:

    > cd events-sync
    > meteor

    Then we can see our app running on the browser by running http://localhost:3000.

    Adding in React Components

    We need to add in some npm packages for react.

    > meteor npm install --save react react-dom

    Replace the contents of /clients/main.html with this piece of code:

    <head>
      <link rel="stylesheet href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
      <title>Event Sync</title>
    </head>
    <body>
      <div id="app"> </div>
    </body>

    Then we replace /clients/main.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Meteor } from 'meteor/meteor';
    
    import App from '../imports/ui/App.js';
    
    Meteor.startup(() => {
      ReactDOM.render(<App />, document.getElementById('app'));
    });

    We'll add in a folder /imports/ui to the root of our project folder, where our React code will live in. We are using the app structure recommended by Meteor. The gist is, everything outside the /imports folder is loaded automatically on startup. Files inside the imports folder are lazy loaded. Only loaded when they are explicitly imported in another file. Find more literature on this here.

    So inside /imports/ui we can add a file, App.js

    import React, { Component } from 'react';
    
    class Todo extends Component {
      render() {
        return (
          <div>
            Hello World
          </div>
        );
      }
    }
    
    export default Todo;
    

    If we go to our browser now we should see Hello World printed on our screens. Just like that we have our first piece of React successfully rendering with Meteor.

    Event App

    Now that we have the most basic parts of the project up and running. Let's get into creating the event app.

    We want our users to be able to:

    1. Create an event
    2. View these events
    3. Delete events
    4. Edit/Update event details

    Lets start by creating events.

    Creating Events

    React Components

    We'll create a form for entering new events into our app. Let's create a new file inside /imports/ui called AddEvent.js.

    
    > cd imports/ui
    > touch AddEvent.js
    
    
    import React, { Component } from 'react';
    
    class AddEvent extends Component {
      constructor(props) {
        super(props);
        this.state = {
          title: "",
          description: "",
          date: ""
        }
      }
    
      handleChange = (event) => {
        const field = event.target.name;
    
        // we use square braces around the key `field` coz its a variable (we are setting state with a dynamic key name)
        this.setState({
          [field]: event.target.value
        })
      }
    
      handleSubmit = (event) => {
        event.preventDefault();
    
        // TODO: Create backend Meteor methods to save created events
        alert("Will be Saved in a little bit :)")
      }
    
      render() {
        return (
          <div>
            <div className="text-center">
              <h4>Event Sync</h4>
            </div>
            <hr />
    
            <div className="jumbotron" style={{ margin: "0 500px" }}>
              <form onSubmit={this.handleSubmit}>
    
                <div className="form-group">
                  <label>Title:</label>
                  <input
                    type="text"
                    className="form-control"
                    placeholder="Enter event title"
                    name="title"
                    value={this.state.title}
                    onChange={this.handleChange}
                  />
                </div>
    
                <div className="form-group">
                  <label>Description:</label>
                  <input
                    type="text"
                    className="form-control"
                    placeholder="Enter event description"
                    name="description"
                    value={this.state.description}
                    onChange={this.handleChange}
                  />
                </div>
    
                <div className="form-group">
                  <label>Event Date:</label>
                  <input
                    type="text"
                    className="form-control"
                    placeholder="Enter date in the format mm.dd.yyyy"
                    name="date"
                    value={this.state.date}
                    onChange={this.handleChange}
                  />
                </div>
    
                <button type="submit" className="btn btn-primary">Add Event</button>
              </form>
            </div>
          </div>
        );
      }
    }
    
    export default AddEvent;
    

    Note: We are using bootstrap, you might have noticed that already, and some inline styling. Bootstrap works because we added the bootstrap link tags into our /client/main.html file during our setup.

    We have our AddEvent component. It is a simple form with three input fields and a submit button. On change of the input fields we save the text into the components state. On submit (when we click the button Add Event) we get an alert on the browser. We need to figure out a way to persist data in order to save the events we create, lets switch gears and delve back into Meteor.

    Meteor Collections

    In Meteor, we access MongoDB through collections i.e we persist our data using collections. The cool thing about Meteor is that we have access to these collections both in the server and the client. Which essentially means less server side code is needed to access our data from the client. Yay, us!!

    Server-side collections: they create a collection within MongoDB and an interface that is used to communicate with the server

    Client-side collections: do not have a direct connection to the DB. A collection on the client side is a cache of the database. When accessing data on the client side we can assume that the client has an up-to-date copy of the the full MongoDB collection.

    If you want to read up more on how collections work you can have a look here.

    Now that we have some understanding on how collections work lets define our collections.

    We'll create a new folder in the imports folder /imports/api and call this file events.js.

    import { Mongo } from 'meteor/mongo';
    
    // Creates a new Mongo collections and exports it
    export const Events = new Mongo.Collection('events');

    We then need to import our collection into our server. So inside /server/main.js add this line:

    import '../imports/api/events.js';
    Getting data from a collection into a React component

    To get data into a React component we need to install a package that will allow us to take Meteor's reactive data and feed it into a React component. There are a lot of cool npm packages out there, honorable mention to react-komposer. Find a really cool article on how to set it up here. We'll use an Atmosphere package for this article react-meteor-data.

    Note: I picked this package mainly because it was the most recently updated package on github. Feel free to experiment with others and add comments on how your experience using them was.

    > meteor add react-meteor-data

    We then use a HOC (Higher Order Component), withTracker, to wrap our component.

    Lets replace our /imports/ui/App.js file with this:

    import React, { Component } from 'react';
    import AddEvent from './AddEvent';
    // we import withTracker and Events into our app file
    import { withTracker } from 'meteor/react-meteor-data';
    import { Events } from "../api/events";
    
    // Create a new React Component `EventApp`
    class EventApp extends Component {
      render() {
        return (
          <div>
            <AddEvent />
            {/*
              we have the pre tags to view contents of db just for verification
            */}
            <pre>DB Stuff: {JSON.stringify(this.props, null, ' ')} </pre>
          </div>
        );
      }
    }
    
    // Wrap `EventApp` with the HOC withTracker and call the new component we get `App`
    const App = withTracker(() => {
      return {
        events: Events.find({}). fetch()
      }
    })(EventApp);
    
    // export the component `App`
    export default App;

    Feeling overwhelmed yet? Not to worry we'll go over what happens, keeping it short and as simple as possible.

    Higher Order Components: are basically functions that take components and return new components. This is very useful coz you can do some cool stuff like:

    • Render highjacking
    • Code reuse
    • State manipulation
    • Props manipulation (just highlighting some use cases of HOCs don't get too caught up in this)

    So what withTracker does is; it takes the component you pass in it, in our case EventApp, and returns a new component that passes down events as a prop. This new component is what we have instantiated as App.

    Long story short: we have two components App and EventApp, App is the parent component to EventApp. The App component reads data from our collection and returns it as a variable events. App passes down events to EventApp as a prop. Every time the database contents change events gets new data and the EventApp component re-renders. Therefore, we get data from our Meteor collection into our React components in a reactive way.

    Phew! Thats that. Let's test our code out. Switch to the browser on localhost:3000 and you'll see that events is currently an empty array.

    Let's manually add something in our db.

    > meteor mongo

    meteor mongo command will open a console. In that console type in:

    > db.events.insert({ title: "Hello world!", description: "", date: new Date() });

    If you check your web browser events is now populated with the new entry. Amazing right, basically we didn't need to write a whole lot of code on the server to get this data. You can keep playing with the database to experience the reactivity Meteor allows us. Any change to the db is automatically populated on the client.

    Lets add this functionality onto our submit button. Inside imports/ui/AddEvent.js add the following code.

    import React, { Component } from 'react';
    // import Events collection
    import { Events } from "../api/events";
    
    class AddEvent extends Component {
      ...
    
      handleSubmit = (event) => {
          // prevents page from refreshing onSubmit
          event.preventDefault();
    
          const { title, description, date } = this.state;
    
          // TODO: Create backend Meteor methods to save created events
          // alert("Will be Saved in a little bit :)")
    
          // add method `insert` to db
          Events.insert({
            title,
            description,
            date
          });
    
          // clears input fields onSubmit
          this.setState({
            title: "",
            description: "",
            date: ""
          })
        }
    
      render() {
        ...
      }
    }
    
    export default AddEvent;
    

    Now when we fill in the form we created and hit the Add Event button we actually add data into our db and see it displayed in our pre tags under the form.

    Displaying Events

    Now that we have persisted our data lets display it on our page. This calls for a new file, /imports/ui/ListEvents.js.

    import React, { Component } from 'react';
    
    class ListEvents extends Component {
      render() {
        return (
          <div>
            {this.props.events.length ? this.props.events.map((event) => (
              <div className="list-group" key={event._id} style={{ margin: "20px 100px" }}>
                <div className="list-group-item list-group-item-action flex-column align-items-start">
                  <div className="d-flex w-100 justify-content-between">
                    <h5 className="mb-1">{event.title}</h5>
                    <small>{event.date}</small>
                  </div>
                  <p className="mb-1">{event.description}</p>
                </div>
              </div>
            )) : <div className="no-events">OOOPSY: NO EVENTS REGISTERED</div>}
          </div>
        );
      }
    }
    
    export default ListEvents;

    Then we import this in our /imports/ui/App.js file.

    import React, { Component } from 'react';
    ...
    // Add ListEvents
    import ListEvents from './ListEvents';
    
    class EventApp extends Component {
      render() {
        return (
          <div>
            <AddEvent />
            {/*
              we have the pre tags to view contents of db just for verification
              <pre> DB STUFF: {JSON.stringify(this.props, null, ' ')} </pre>
            */}
    
            {/*
              pass in props into the component (this is where `events` live in)
            */}
            <ListEvents {...this.props}/>
          </div>
        );
      }
    }
    
    ...
    
    export default App;
    

    If jump back to our browser you will now see a list of the events printed under your Add Event form. Try adding some more events and notice that they reactively show up on your page.

    Updating and Deleting Events

    Given the knowledge we now have I think deleting and updating events will be easy all you need is to add some buttons and add some db methods on your components and thats it. You can try writing out these components and methods by yourself if you get stuck have a look at the complete app in the repo here. Feel free to ask questions down on the comments and/or on twitter.

    Code Refactor and Optimization

    With the code we have running now we see that every time events change on the database we re-render the EventApp component. This means both the AddEvent and ListEvents components re-render (and potentially every other component we render on EventApp). This right now isn't such a big deal coz we are running a tiny app. We however, need to keep in mind that our apps should run optimally for better performance.

    All we need to re-render at the moment on change of the database fetch is the ListEvents component. As its the only component using the events props at the moment. This means we can move the withTracker HOC into the ListEvents component and have the app working as before but more optimally.

    So, let's do this:

    In /imports/ui/ListEvents.js we add this:

    import React, { Component } from 'react';
    // import necessary files
    import { withTracker } from 'meteor/react-meteor-data';
    import { Events } from "../api/events";
    
    class ListEvents extends Component {
      render() {
        return (
          ...
        );
      }
    }
    
    // add `withTracker` HOC and pass in `ListEvents` and export the new component
    export default withTracker(() => {
      return {
        events: Events.find({}). fetch()
      }
    })(ListEvents);

    Remove the tracker from /imports/ui/App.js

    import React, { Component } from 'react';
    import AddEvent from './AddEvent';
    import ListEvents from "./ListEvents";
    
    class App extends Component {
      render() {
        return (
          <div>
            <AddEvent />
            <ListEvents />
          </div>
        );
      }
    }
    
    // export the component `App`
    export default App;

    That's it!! We now have a simple Event App that works optimally.

    Replacing Blaze templates with React

    Some times you may already have a whole Meteor project running built with Blaze templates and all you want to migrate your app to React.

    You can add in your React Components into Blaze easily by adding react-template-helper.

    Displaying React in Blaze

    First run:

    > meteor add react-template-helper

    Then on your template you can add in you React Component:

      <template name="events">
        <div>Hello</div>
        <div>{{> React component=AddEvent }}</div>
      </template>

    Lastly, you need to pass in the component into the template using a helper:

      import { Template } from 'meteor/templating';
      import './events.html';
      import AddEvent from './AddEvent.js';
    
      Template.events.helpers({
        AddEvent() {
          return AddEvent;
        }
      })

    Passing in props and/or callbacks:

      import { Template } from 'meteor/templating';
      import './events.html';
      import { Events } from '../api/events.js';
      import AddEvent from './AddEvent.js';
      import ListEvents from './ListEvents.js';
    
      Template.events.helpers({
        ...
        ListEvents() {
          return ListEvents
        }
    
        events() {
          return Events.find({}).fetch();
        }
    
        onClick() {
          const instance = Template.instance();
          return () => {
            instance.hasBeenClicked.set(true)
          }
        }
      })
      <template name="events">
        <div>Hello</div>
        <div>{{> React component=AddEvent }}</div>
        <div>{{> React component=ListEvents events=events onClick=onClick }}</div>
      </template>

    Those are the basic additions you need to have your React components up and running in Blaze.

    Conclusion

    There we have it, a working Event App using Meteor and React. This tutorial encompasses just the basics on set-up and how to get React up and running with Meteor; there are a whole lot you can achieve using Meteor and React. I hope that this article has spiked some interest in learning more and delving deeper into this world.

    There are some great open-source projects that use Meteor and React that you can contribute to and learn from. Find the link to an example repo here.

    Feel free to post questions and suggestions on the comment section!!

    Joy Warugu

    7 posts

    Software engineer, stationed in Nairobi, Kenya.

    Main stack => Javascript. Front-end dev => ReactJS.

    YOLO!