Structuring a Complex React/Redux Project
Structuring a Complex React/Redux Project
Here the Nylas team goes through the code that they used to create their recently updated Nylas Dashboard and breaks down each element.
Join the DZone community and get the full member experience.Join For Free
Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.
The Nylas engineering team recently updated the Nylas Dashboard, giving everyone on our customer's teams—from developers to product managers, sales engineers, customer success reps and finance teams—the ability to access their Nylas API account.
After our blog post about increasing developer speed during the revamp of the Nylas Dashboard, we had some follow-up requests for a) the scaffold script we wrote, which is now available here, and b) the general structure of the project.
Our dashboard front-end code is a React/Redux application, and the general structure looks something like this:
src/ appConstants/ components/ DropdownMenu/ dropDownArrow.png index.js index.test.js stylesheet.js ... containers/ higherOrderComponents/ layouts/ models/ modules/ screens/ store/ accounts/ actions.js constants.js endpoints.js reducers.js selectors.js applications/ ... actions.js api.js configureStore.js rootReducer.js selectors.js index.css index.js registerServiceWorker.js Routes.js
There's a lot going on here, so I'll briefly break down what each directory or file is for.
appConstants/ is simply where we kept any application-wide constants, like API keys for third party services. We originally named this
constants/, but it turned out that there was another constants node module elsewhere in the project that caused naming conflicts, so we renamed it to
We broke up our React components into multiple directories to try to keep things grouped in a more manageable manner. We initially only had a split between presentational components and containers. The important distinction between these is that presentational components are stateless while containers are not. You can learn more about the difference between presentational components and containers from this article. However, as we continued to add more and more components, we needed more separation. The directories we ended up with are:
components/- the original directory for presentational components. Most of our presentational components still live here.
containers/- the original directory for containers. (I bet you couldn't have guessed that one!)
higherOrderComponents/- Higher Order Components (HOCs) are a special type of container that are actually functions. These functions encapsulate reusable logic patterns and are used to wrap other components with that logic. For instance, one of our HOCs is a LazyLoaded component. This displays a loading indicator before the necessary data is loaded, and reports back to us if it takes too long. We pass any screens that need this loading behavior through the LazyLoaded HOC rather than having to re-implement the behavior within each one!
layouts/- This is the only other directory that holds presentational components. These presentational components are specifically concerned with how an entire page in our application is laid out.
screens/- Screens are containers that pull in all of the presentational components and sub-containers for a particular application view. All of our screens start with a layout component and add children from there.
Each component has its own subdirectory within one of those parent directories. The main file in each subdirectory is
index.js, which is where the general component definition goes.
index.test.js is the test file that we automatically add via our scaffolding script. We also keep any styling for the component in this subdirectory. This includes any images it needs and a separate
stylesheet.js file if the styles get too bulky to keep in
/models is where we defined classes for each of our API objects. Each class defined a
toJSON() and a
fromJSON() method which allowed us to transform JSON responses into instances while we worked with them inside the application, and then back to JSON when we had to send the data back to our servers. The project also uses Flow as a type checker, and transforming the JSON into more concrete data structures allowed us to properly type-annotate each field.
/modules is basically a directory for utility or helper code. We grouped closely-related code into their own files and ended up with modules like
store/ is for all of our Redux code. As I mentioned in my previous blog post, we separated our store into subdirectories for each of our models. Each of these subdirectories had the traditional Redux files of
selectors.js. We additionally had a
constants.js file for any constants relevant to that model store, and
endpoints.js for functions that interact with our back-end API. At the
store/ root, we have files that import all of the functions from the corresponding subdirectory files:
actions.jsimports from all of the sub
api.jsimports from all of the sub
rootReducer.jscombines all of the sub
selectors.jsimports all of the sub
We also have
configureStore.js which does the initial setup of actually creating the store and potentially loading any previously saved state.
index.css holds our over-arching CSS styles. Most of our styles are inside our component directories, but there are a few
html level styles that live in this file instead.
index.js simply renders our root React component.
registerServiceWorker.js sets up service workers so that we can serve assets from a local cache to make our application run faster.
Routes.js connects each of our screen components to an application route. For example, this is where we register our
RegisterScreen to be loaded when the user visits the
/register route in our dashboard.
Overall, we tried to structure our project such that all of the relevant code is nearby when we're working on a specific part, while still maintaining a separation of concerns. Keeping smaller files grouped by model or component really helped improve the developer experience. We may continue to iterate on our structure in the future, but so far this has worked well for us! Let us know if you have any questions or if you do things differently at your company.
Published at DZone with permission of Halla Moore , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.