We have launched a number of different single-page React/Redux apps on VA.gov, in addition to building digital forms using a form-builder library that reuses the same code to run multiple React apps for different forms. This document is an attempt to begin collecting best practices that the team has and continues to lean toward when architecting and developing front-end applications.
Components vs. containers
A common React/Redux application architecture is to divide your React components into two types: regular components and container components. These are also sometimes referred to as a dumb and smart components. Container components connect to the Redux store using the
connect function and map a specific part of the state object to the props of a React component. Regular components are just plain React components; they take in props and they can have internal state (though we generally avoid this; see below).
In general, we try to use regular components whenever possible and only a few container components. The reason for this is because tying a component to the Redux store couples it to a particular slice of the state of your application, as well as coupling it to a particular way of organizing your state. So refactoring a lot of container components can be difficult. Debugging can also be difficult with a lot of container components, because they interrupt the usual flow of data down through components. Instead of all data being passed down via props from a single component at the top of the component tree, intermediate components might pull in different parts of the Redux state and pass down that data as props to other components, creating a mix of data combined from different connections to the Redux state.
There are benefits to using container components, though. It can be painful to pass lots of props all the way down to the leaf components in a component tree and container components allow you to "reset" and grab specific data from the store without all that wiring. They can also improve performance, because passing props down from the root of the component tree means that all intermediate components will re-render whenever data changes. Container components can send data down to their children without all the parents of the container component re-rendering.
On VA.gov, we normally use a single container component per page (or independent widget, like login), and only use other container components if there's a compelling reason for doing so. Our apps have a
containers folder and a
components folder that we divide components between.
Using setState in React components
We also generally avoid
setState inside regular components. This isn't because
setState is bad, necessarily, but because it can be hard to track down data changes due to
setState when you're expecting all changes to go through the single Redux store. It can also be tricky to keep that state in sync with the data from the Redux store passed in as props. So, when we do use
setState, it's typically for ephemeral UI state, or state that would be more difficult to follow if it were put in the store and passed down through props.
Keep in mind that these are general conventions, not iron-clad rules, and we should revist them as we gain more experience using React and Redux.