Structuring a Complex React/Redux Project

hallamoore

Halla Moore

Posted on March 8, 2018

Structuring a Complex React/Redux Project

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
Enter fullscreen mode Exit fullscreen mode

🙀 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 appConstants/ instead.

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 index.js. We used Aphrodite to be able to write our styles in JavaScript, which helped us keep the styles localized to each component rather than buried in massive CSS files.

/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 errorReporter.js and apiRequest.js.

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 actions.js, reducers.js, and 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.js imports from all of the sub actions.js files
  • api.js imports from all of the sub endpoints.js files
  • rootReducer.js combines all of the sub reducers.js files
  • selectors.js imports all of the sub selectors.js files

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 body and 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.


This post was originally published on the Nylas Engineering Blog

💖 💪 🙅 🚩
hallamoore
Halla Moore

Posted on March 8, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related