Let’s face it: developing scalable front-end code isn’t piece of cake. No matter how well-structured the framework/architecture you’re using is, everything will be converted to ye olde HTML, CSS and (vanilla) JS.
Well, the good news is the open-source community already created solid frameworks (like AngularJS and ReactJS) to make your life incredibly easier, so you can work on high-level code and nevermind the hardcore stuff.
This brings up a new scenario, though – after getting used to building things the old-fashioned way (or not-so-old-fashioned, with tools like Backbone.js or Ember.js), you’ve decided to try what is trending on front-end development now: ReactJS, using the Redux architecture. You got excited with the possibilities this opens (and – oh my! – the ability to ditch jQuery once and for all), and, after working for a few weeks you realize that your code is a total mess.
Don’t be alarmed – you can fix that. I’ll share a few insights on how I transformed my Frankenstein into a clean and Redux-compliant codebase, while still being able to deliver new features.
The insights from this article might also be useful for seasoned developers, who want some tips on how to achieve a better-organized and scalable code.
1. Define the role of your components
When starting with React+Redux, people tend to build massive components, which contain a whole bunch of logic and connect to many Store states. It is not uncommon to find people treating components as HTML templates — you have one for each page, and you simply translate the HTML code into JSX in the render() function.
Developing things in such way invalidates one important advantage of this pattern, which is the ability to reuse components throughout your web application, and propagate state changes when they affect more than one component or page.
Presentational Components (often referred to as ‘dumb components’) are all-normal ReactJS components, which do not contain any logic or store connection – they are simply ‘views’ to display data. These components usually receive some props and display them or trigger actions depending on the user behavior (the actions should be sent as props to the components, as no business logic is defined on presentational components).
Container Components (often referred to as ‘smart components’), on the other hand, don’t do anything visual: they are the ones who’ll process and pass data to the presentational components. Containers will be the components that connect to the Store and import actions, so you’ll often see mapDispatchToProps() and mapStateToProps() definitions inside them.
In most cases, developers tend to structure the entire application as a set of container components. In order to fix this, two steps have to be taken:
1.1. Break your containers into presentational components and container components
This will enable you to create several pieces of reusable code, so that your front-end will have a consistent look, and you’ll force yourself to avoid duplicated code to address similar problems.
Taking this blog’s index as a reference:
We could split it into the following components and containers:
You can observe that it is possible (and fairly common) to put a container inside a container, in order to modularize and structure the code.
1.2. Center the communication in the containers
Your container components are the ones responsible for knowing which actions should be triggered and what state updates they need to receive.
Think of containers as structural components: they are fundamental for your code, but they don’t mean anything visually — they just define what data will be presented by the presentational components, and how they should behave.
2. Decide what state information actually needs to be in the Store
The idea of having a globally accessible Store with all the variables from the system might sound promising, but listen to me: it’s a trap!
Store states should be used with caution, and not for anything that is used as a presentation variable. In other words, it makes sense to store the username and avatar link globally, but the enabled/disabled status of a button in a form might as well live as a local state of the container component.
There is no 100% accurate rule to define when to use the Store or not, but you should take into account the implications when you decide to go down that road. The Store does not clear data automatically when your router changes context, which might cause unexpected behavior when you go back to a context where you’ve already taken actions.
To make this less abstract, let’s think of a search engine where you store the number of the result page and the search term in the Store. You perform a search, go to the third page of results, then click on the ‘home’ button (usually the logo) to return to the root search page. Guess what? Your search term is pre-populated in the input box. You delete it, enter a new search term and click on ‘search’. Suprise, again: you’re already on the third page of the search results. Weird, right?
This is because the application state (Store) will persist as long as you keep the web application open, without caring about expiry time or user behavior. Unless you explicitly clear the data used in a given context, it will remain there and show up when you less expect. I’m not saying this is always undesirable, but those edge cases must be well-thought when defining what should be put in the Store.
3. Consider sharing your CSS components’ namespaces
Many people think of React/Redux as a new mindset for JavaScript and HTML, and think of CSS as something completely apart – continuing to develop it the old-fashioned way, i.e., everything in a single CSS (or SCSS, at most) file.
Whether you should use a React-Friendly JavaScript CSS library to write inline CSS, or simply use well-defined CSS Architecture is entirely up to you – just keep in mind that the naming of each component should be consistent on both JS and CSS. This way, you’ll be able to easily find where the styling of a specific component is defined, and will avoid merge conflicts when many developers are working in the same codebase.
Wrapping up
Improving your code is a continuous effort – and realizing it needs to be done is a sign that you’re constantly evolving your skills and mindset. It is usually a good idea to refactor your code gradually, while still delivering new features. I hope this post helps you improve your React code – please share your opinion in the comments below.
Edit: removed the GIFs as many readers suggested – thanks for the input, guys! 🙂
Progressive rock lover, programming languages aficionado, full-stack web/mobile developer and pentesting enthusiast. Legend says he's never lost a Pokémon battle.