Where does Redux fit in Meteor?


What’s this post about?

Redux has become very popular recently and shown a lot of potential. However, considering the existence of Meteor’s Tracker and Minimongo, it’s unclear as to where Redux fits in Meteor apps. This post aims to start a discussion about how Redux can fit in a Meteor app. I’m interested in receiving feedback about what you think is the most suitable way of doing this.

What are Redux, Tracker, or Minimongo?

Before we continue, let’s go through a quick overview of Redux, Tracker, and Minimongo. If you’re already familiar with these technologies, you can skip this section.

  • Tracker 🔗
    • Meteor comes with a dependency tracking system that can watch data sources such as session variables or the result of database queries and re-render templates or do other computations whenever there’s a change. This allows for things such as when a reactive variable changes its value, the template gets re-rendered to display the new value without any extra work.
  • Minimongo 🔗
    • Meteor keeps a local cache of a subset of the database on the client side, which will stay up to date with the backend. This implies that a change in the DB gets synced with the client’s cached version of the database, so that Tracker can pick that up and re-render the template.

  • Redux 🔗
    • Redux is a predictable state container for Javascript. In other words, it’s just an object that holds the state of the application, with principles governing how it can be manipulated. For example for a todo app, the object can look something like this:
      {
        tasks: [ {
          name: “task1”
          state: “completed”
        }]
      }
      
  • You get the idea. Redux is NOT included in Meteor, but many have been excited about what it can bring to the table, while there’s still doubt if it actually adds any value to meteor.

  • Redux uses Actions and Reducers. Actions explain that something happened (e.g. “Add new todo”), and Reducers explain what changes in application’s state in response (e.g. adding an item to the tasks array).

  • It’s worth noting that one of the Redux principles states that reducers, which modify the state, must be pure functions. This means that they don’t mutate the state and do not have any side effects. This is noteworthy because Meteor methods are not pure functions, which leads to interesting situations in some libraries where Meteor methods are used as reducers and Redux principles are broken.

    To learn more about redux check out this tutorial created by, Dan Abramov, the creator of redux.

    Why do I need redux in my Meteor app?

    You don’t. Simpler apps may be fine with using ReactiveDict, Reacts setState (assuming you use react) and other techniques. However, as things get more complex, it’s nice to have one place that gives you a bird’s-eye view of the entire application. Which navigation is active? Which view is the user looking at? These can be expressed painlessly with redux. Furthermore, doing things the React way and compatibility with Apollo can be motivators for introducing Redux in your next Meteor app.

image source: www.eyezmaze.com

Where does Redux fit in my Meteor app?

With the involvement of Meteor, it’s not easy to see where Redux comes into play. Does it replace Tracker and Minimongo? If not, does Redux duplicate the data in minimongo? How is the data in Redux updated? All of these depend on how Redux is implemented in the Meteor app. As of right now, there are a few different approaches, and each one answers the questions above differently.

In this post, we are going to focus on a method described by Abhi Aiyer in an awesome 5 part tutorial. This method works along with Minimongo and Tracker, while other methods may take them completely out of the equation.

In our preferred method, Redux is meant for keeping the UI state. That’s things like which navigation item is active, which view is open, etc. However, while Redux can be used for managing Domain State, in Meteor Mongo and Minimongo can take care of that.

Consider the TODO app in Abhis TODO app tutorial. We will provide a high level overview of how things are tied to each other in that app (Github link).

Meteor methods

Imagine we were to create a simple TODOs app. First, we need Meteor methods that update the database. I will be using a more descriptive name than the ones used in the repository to distinguish these from other similar methods.

  • (1): addTodoMeteorMethod(text) <== Insert new todo item into the database
  • (2): toggleTodoMeteorMethod(id) <== Toggle the completed/incomplete attribute on the todo item in the database

Action Creators

Next, we need to create Action Creators. Action Creators are methods that return an Action, like below. An action is a simple object indicating something that should happen:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  }
}

However, in our case, thanks to redux-thunk, which allows returning async functions as actions rather than simple objects, our action creators return the two functions (1), & (2) that are defined above.

  • (3): addTodoActionCreator(text) <—— Returns function (1)
  • (4): toggleTodoActionCreator(text) <—— Returns function (2)

Note that these action creators are not pure functions since they are using Meteor methods, and altering the database, therefore they both have side-effects. But that is fine. It’s our reducers that need to be pure functions.

Reducers

Remember that reducers take a state and an action, and return a new state:

(previousState, action) => newState

We are going to introduce two reducers that help the UI experience by filtering and paginating the todo list:

  • (5): visibilityFilterReducer(previousState, action)
  • (6): pageSkipReducer(previousState, action)

Both of these functions pretty much return the action that is passed to them. For example, consider calling our reducers with the following action:

 {type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED'}

The reducer returns “SHOW_COMPLETED”, which is the next state:

Store

All these things seem nice, but where are the states actually kept? The Store is where all the magic happens. Store is in charge of three things:

  • Holding and returning application state
  • Letting listeners subscribe to changes (we use mapStateToProps for this)
  • Dispatching actions and side-effects
    • (3), (4), (5), and (6) all get dispatched via the store like so:
      • Store.dispatch(addTodo(‘new task’)); <== uses (3)
      • Store.dispatch({ type: ‘SET_VISIBILITY_FILTER’, filter: ‘SHOW_COMPLETED’ }); <== uses (4)

React:provider

We established that all actions have to go through Store. But how do things tie to the frontend (i.e. React)? The Provider does that using connect. The is a React component in React-Redux which has to be wrapped around all the other components so that it can pass Store down to child components.

React-redux:connect

As mentioned above, connect is the bridge between React and Store.

  • Connect provides access to Store.dispatch through React Props so that sideEffects and actions can fire. This enables us to, for example, dispatch an addTodo from React when a button is pressed.
  • Connect provides access to subscribe via props that are reactive via mapStateToProps, which is a simple function that maps the Redux state to React Props so that they can be used in the component.

The code for mapStateToProps is as simple as below:

function mapStateToProps(state) {
  return {
    visibilityFilter: state.visibilityFilter,
    pageSkip: state.pageSkip
  }
}

What happened to Minimongo?

In the application described above, we managed things like filters and page numbers (UI State) with Redux, but we kept our data (Domain State) in Mongo and Minimongo. This means that the data was still accessed via Meteor’s Subscribe and Publish. However, one can also keep the domain state on the Redux Store, using Tracker to keep it up to date.

// will run every time Messages changes
Tracker.autorun(() => {
  store.dispatch({
    type: 'SET_TODOS',
    todos: Todos.find().fetch(),
  });
});

Note that Mongo.Collection.Observe can be used instead of tracker for a more efficient and powerful solution, but Tracker is in this example to convey the idea.

Alternative solutions

What was described above is one of the ways that Redux fit within a Meteor app. However, there are other ways that can work just as good, but use different approaches.

  • Redux-DDP can be used Instead of the Tracker hack above. This approach uses DDP event listeners to update the app state. However, given the ambiguous future of DDP in Meteor, this may not be the most future proof solution.
  • Using ReactiveDict: This approach mimics the behaviour of Redux using ReactiveDict, which in the end achieves similar behaviour. Note that in this approach the Reducer has side-effects and is not pure, so it breaks the Redux principles.

Given what we observed above, there isn’t a single best approach for where Redux should live within a Meteor App and what things it should be in charge of. There are arguments for and against each approach. I’m interested in hearing if you think Redux is relevant given what Meteor already offers and if so where it should fit.

Let's stay connected. Join our monthly newsletter to receive updates on events, training, and helpful articles from our team.