React is great. I've been using it for almost 7 years now, and every new app I build feels like diving into a box of Lego. You've got little blocks to play with, to compose with, and you can build great things with it.
But let's face it: sometimes, you've got to step up your game, and add some logic to your masterpiece. You've got to think ahead and ask yourself the one question that separates good apps from great ones:
What if it fails ?
And that is exactly why you've got to separate your logic from your views.
Back in the glorious days of MVCs (which stands for Model View Controller), the very idea of separating your logic and your views seemed pretty obvious. But with new libraries, especially with SPAs, things can get messy. Sure, you've got a lot of modules to help you fetch data from APIs, but where exactly do you draw the line between fetching this data, and using it within a component?
Here's a bit of insight to build proper containers/views.
Structure is everything
So, you're working on a new component. You're supposed to fetch some data from an external API, and display it within a box. Let's use the wonderful jsonplaceholder
API to mock up something nice. Take a look at the following code:
So far, so good: we get a list of items. Now, let's be honest: sometimes, sh*t happens. It might be for a lot of reasons:
- API is down
- Bad request formatting
- Unexpected data format
All of these cases have to be taken into account, and your logic should be updated accordingly.
Think of it as a list of possible outcomes. Let's say you're working on an online shop. You're fetching a list of products, and you want to display it.
Instead of thinking: "I want to display my products", start thinking around our what if it fails question:
- What if the request fails?
- What if it succeeds, but returns an empty array of products?
- What if my API response is not properly formatted ?
All of these questions imply a logic that has to be separated from our Component. So, let's update:
So, the result looks exactly the same, right? But let's take a look at the files: instead of having a single file taking care of both the logic and the rendering, we now have a container and a component.
Take a look at containers/TaskList/index.js. We made sure to handle most cases for our request: if it fails, we'll have a fallback. If it succeeds, we can pass the data onto our Component.
Our component components/TaskList/index.js has also received an upgrade: we can render different views depending on our props.
Pros and cons of a Contained Logic
Let's start with the cons:
- It takes some time to get used to it
- It takes longer to set up.
Now, the benefits:
- First of all, error handling. By separating your logic and your view, you can handle your errors from afar, and update your component accordingly.
- Reusability. Let's say you're building a React web app, and later on, you want to build the mobile version, using React Native. Obviously, the Component will use a different syntax, but the data is handled just the same. You can simply import your containers within your React Native app, your logic will be the same.
- Testing. If you're working in a TDD environment, creating your containers would be the step right after writing your tests. Again, this can be useful for exporting both your logic and your tests, from one app to another.