Estimate project

Structure Container Components React Meteor Best Practices

Software Development   -  

June 21, 2016

Table of Contents

FYI: This is one of three topics of our first Meteor Meetup on June 4th, 2016, container components react to Meteor best practices. The author is Khang Nguyen, a young talent member of Designveloper. This is an article on his blog, you can read the original article here.

Introduction to Container Components React Meteor Best Practices

Over the past two months, I have been working on an interesting project at my company which is supposed to be a very big one. So at first, as a team leader and core maker, I spent quite a lot of time finding a good starting point.

meteor react

I meant stuff like how to structure the project, which frontend stack to use, and how that stack connects to the server. How to set up and bring all those things together, etc, to satisfy all the project’s requirements.

Finally, I settled down with Meteor for the backend, React for the frontend, and Vanilajs ES6 MVC for the app extension (this was the most interesting part of the project. I will talk about it in another blog). At first, things went well and smoothly. Needless to say that React was so amazing that I completely changed my thoughts when programming.

The 5 Container Components React Meteor Best Practices

But soon later, since this was the first time I had really built such a big project with React, things started to get messy. I encountered a problem that every big project coded in React had. Hence, I have this post written to let you know what the problem was and how I attempted to solve it.

1. React – Prerequisites

React is introduced to be simple and very easy to learn for beginners. That is definitely true, but frankly speaking, it takes quite an effort to come up with when you are at the beginner level.

Unfortunately, this post is not aimed at beginners because I am writing about an experience I learned in a big project, not just the way to use React for the Meteor project. So it may require readers to have the following knowledge to understand what I am going to write:

2. The problem that I encountered

I can summarize the problem with only one word, and that is maintainable. Following the unidirectional data flow principle, I created a container component for each route for the problem.

This component is in charge of almost every logic stuff for the route it is being used for. E.g. it takes care of subscribe data/call methods from the Meteor server, and is a middle man for its children components talking to one another. With all those missions, soon it becomes a very big and fat guy.

The file in which I coded the container component was about 700 lines when I just finished about half of the work. This was clearly unacceptable. If I had ignored this problem and kept coding to get the job done fast, it would have been a nightmare for the maintainer/debugger.

Knowing that this was my company’s main project, meaning that my colleagues and I would be working on it for years, I stopped coding features and started reviewing the code.

Please check out the list of "Meteor for Beginners - Easy and Practical" videos:

3. First attempt

I looked at all the container’s functions and realized that they could be categorized into four groups:

  • Component’s must-have functions: getInitialState, getMeteorData, render, etc…
  • Handler functions: all event handlers for all container’s children components
  • Helper functions: functions to help get necessary data for container’s children components based on container props and state. These functions are merely pure functions, they take input and produce output without touching the container state or talking to the Meteor server.
  • Logic functions: functions to modify the component state call methods from the server. These functions are called by event handlers in response to user actions.

With this model in mind, I decoupled the last three groups’ functions into three separate files and imported them back into the container through mixins.

The directory of the container looked like this:

root
|   ...
|---...
|---poppi:client-container-example-component
    |---client
        |---components
            |---subcomponent1
                |   ...
            |---subcomponent2
                |   ...
            |   ContainerExample.jsx
            |   ContainerExampleContainer.jsx
            |   ContainerExampleHandlers.js
            |   ContainerExampleHelpers.js
            |   ContainerExampleLogicFuncs.js
    |   package.js
|---...

And also the code of the container

ContainerExampleContainer = React.createClass({
  mixins: [
    ReactMeteorData,
    ContainerExampleHandlers,
    ContainerExampleHelpers,
    ContainerExampleLogicFuncs,
  ],
  getInitialState() {
    ...
  },
  getMeteorData() {
    ...
  },
  render() {
    ...
  },
});

My container looked much better. They became a thinner and more handsome guy. When you need to find a function, I recommend this decoupling technique for a faster and more semantic solution. However, it would be even better if I did not face a hurdle.

4. Push it a bit further

Even though I separated all functions into 3 different mixin files, I had not had any idea on where to put each function yet. All the time I had to check those 3 functions to identify the appropriate function places. This was time-consuming and I needed a more efficient way. The only way that I thought of at that time was to look at the function calling statement.

After thinking for a while, I fell back to the basic technique widely in js: “namespace”. I grouped all the functions under a namespace obj inside the mixin obj. My three mixin file then became:

// ContainerExampleHandlers.js
ContainerExampleHandlers = {
  Ha: {
    clickHandler() {
      this.L.test1();
      return;
    },
  },
};
// ContainerExampleHelpers.js
ContainerExampleHelpers = {
  He: {
    getData(state) {
      return {
        state
      };
    },
  }
};
// ContainerExampleLogicFuncs.js
ContainerExampleLogicFuncs = {
  L: {
    test1(param1) {
      const data = this.He.getData(param1);
      this.setState({
        ...data,
      });
    },
    test2(param2) {
      Meteor.call('test2', param2);
    }
  },
};

By just looking at the calling function statement, I knew exactly where these functions came from. This is extremely useful and efficient when working on a big and complicated project.

But unfortunately, I still could not enjoy the fantasy of this technique, because the scope inside all functions was wrong. So I had to bind their contexts to the right ones.

5. Ramda to the Rescue

The binding process could be done easily by a for-in loop inside the getInitialState function of the container. But it required more than that and I needed a more unified, modern, handy and understandable way, also a shorter one to use in this big project. Luckily, the rise of Ramda recently got my attention and turned out to be the best fit for my requirements.

Finally, I came up with this binding function:

// bindMixinFunctions.js
bindMixinFunctions = R.converge(function(keys, values, ctx) {
  const bindValues = R.map((val) => R.bind(val, ctx), values);
  return R.zipObj(keys, bindValues);
}, [
  R.flip(R.keys),
  R.flip(R.values),
  R.identity
]);

And use it in action:

ContainerExampleContainer = React.createClass({
  mixins: [
    ReactMeteorData,
    ContainerExampleHandlers,
    ContainerExampleLogicFuncs,
    ContainerExampleHelpers,
  ],
  getInitialState() {
    const thisBindMixinFunctions = bindMixinFunctions(this);
    this.He = thisBindMixinFunctions(this.He);
    this.Ha = thisBindMixinFunctions(this.Ha);
    this.L = thisBindMixinFunctions(this.L);
    ...
  },
  ...
});

After all the hard work, I was able the enjoy the result. The code ran smoothly as expected and was much better in terms of structuring, semantic and maintainable criteria. I did not have to worry about the later maintaining/debugging process anymore.

Conclusion

It may be a bit tedious to write all this code and structure before implementing the real features. But for me it is really worthy, this pattern actually saves me a lot of time and effort trying to figure out where a function is, what it does, and how it connects to other functions.

If you are still using React version 0.14.3 in Meteor 1.2, which is possible if you already worked in these frameworks, I hope that you will get benefited from my pattern and take the best of it.

If you are going to set up a new project with the latest version of React and Meteor and decide to implement this pattern, I would love to hear how you do container components react to Meteor best practices!

Also published on

Share post on

Insights worth keeping.
Get them weekly.

body

Subscribe

Enter your email to receive updates!

name name
Got an idea?
Realize it TODAY
body

Subscribe

Enter your email to receive updates!