loading...

Organizing Your React App Into Modules

jack profile image Jack Williams ・4 min read

First time poster here, long time reader. Figured it was time to give back.

The last two years of my eight years in software development I've done React development. Coming from vanilla Javascript and jQuery, let me say - I'm in love.

This post will detail how we've been organizing our React apps.

This post also assumes you already know how to setup and use React (I.E. this isn't a getting started post).

Let's get to it!

Intro

I'm tired of todos - so our fictional shell app is a bug tracker for game studios to allow alpha and beta testers to report bugs.

It has four modules: Dashboard, Games, Users, Analytics.

Screenshot of fictional app

The beauty of this approach is the parent app has no specific knowledge of the child modules. All it knows is it has modules. Each module manages its own placement and data. You don't have to add <Route /> or <Link></Link> anywhere in the parent App - the children define this.

The code for this post is on GitHub.

GitHub logo jackjwilliams / dev-react-modules

An example of how to organize your react app using modules

This is the sample repository to accompany my dev.to article Organizing Your React App Into Modules.

It shows how you can approach React development using modules.

The fictional shell app we are building is a bug tracker for game studios to allow alpha and beta testers to easily report bugs.

This project was bootstrapped with Create React App.

Available Scripts

In the project directory, you can run:

npm start

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.

The page will reload if you make edits.
You will also see any lint errors in the console.

npm test

Launches the test runner in the interactive watch mode.
See the section about running tests for more information.

npm run build

Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best…

Getting Started

Let's get to some actual coding!

If you don't have create-react-app, install it with npm install -g create-react-app. Then ...

create-react-app dev-react-modules
cd dev-react-modules
npm install react-router-dom --save
yarn start

I won't detail the styling applied, you can view that in the GitHub repo.

Create Modules

Under the src folder, we start out by creating our module structure. It looks something like this:

  • modules
    • Analytics
    • Dashboard
    • Games
    • Users

In each module's folder, add an index.js

src\modules\Analytics\index.js

import React from 'react';

const Analytics = () => (
    <div>Analytics Module</div>
);

export default {
    routeProps: {
        path: '/analytics',
        component: Analytics
    },
    name: 'Analytics',
}

src\modules\Dashboard\index.js

import React from 'react';

const Dashboard = () => (
    <div>Dashboard Module</div>
);

export default {
    routeProps: {
        path: '/',
        exact: true,
        component: Dashboard,
    },
    name: 'Dashboard',
};

src\modules\Games\index.js

import React from 'react';

const Games = () => (
    <div>Games Module</div>
);

export default {
    routeProps: {
        path: '/games',
        component: Games,
    },
    name: 'Games',
};

src\modules\Users\index.js

import React from 'react';

const Users = () => (
    <div>Users Module</div>
);

export default {
    routeProps: {
        path: '/users',
        component: Users,
    },
    name: 'Users',
};

Nothing too fancy here, we've created our modules and their default exports. But instead of only exporting a component - leaving the parent to orchestrate things - we export everything needed for the module to exist. This could be expanded to include the module theme, navigation icon, required permission(s), etc...

What I like about this is that I don't have to change the parent to add a module. I just ... add a module.

Let's break the export down, I've added some comments below.

export default {
    routeProps: { // This gets passed straight to react-router
        path: '/users', // Where the module lives in the nav hierarchy
        component: Users, // The actual component itself
    },
    name: 'Users', // The name of the module
};

You can think of the export structure like a contract between the parent and child module. The parent says I don't care how many modules I have, I just need these things to render you.

Now we need to export all these modules. In the modules folder, create an index.js.

src\modules\index.js

import Analytics from './Analytics';
import Dashboard from './Dashboard';
import Games from './Games';
import Users from './Users';

export default [
    Dashboard,
    Analytics,
    Games,
    Users
];

Here we are exporting a list of modules. Which is all the parent needs.

Create Parent App

Now that our child modules are all complete, let's bring it all together in the main App.js.

src\App.js

import React from 'react';
import { useState } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import logo from './logo.svg';
import './App.css';

import modules from './modules'; // All the parent knows is that it has modules ...

function App() {
  const [currentTab, setCurrentTab] = useState('dashboard');

  return (
      <Router>
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <ul className="App-nav">
              {modules.map(module => ( // with a name, and routes
                  <li key={module.name} className={currentTab === module.name ? 'active' : ''}>
                    <Link to={module.routeProps.path} onClick={() => setCurrentTab(module.name)}>{module.name}</Link>
                  </li>
              ))}
            </ul>
          </header>
          <div className="App-content">
            {modules.map(module => (
              <Route {...module.routeProps} key={module.name} />
            ))}
          </div>
        </div>
      </Router>
  );
}

Let's break it down.

import modules from './modules';

Like I said before, all the parent needs to know is it has modules. Here, we import them.

<ul className="App-nav">
  {modules.map(module => (
      <li key={module.name} className={currentTab === module.name ? 'active' : ''}>
        <Link to={module.routeProps.path} onClick={() => setCurrentTab(module.name)}>{module.name}</Link>
      </li>
  ))}
</ul>

Here, the parent knows that the module has a name and a link (because it's a contract, remember?), so it can dynamically build the navigation menu.

<div className="App-content">
  {modules.map(module => (
    <Route {...module.routeProps} key={module.name} />
  ))}
</div>

Here, the parent also knows the module has a route with a component, so it can dynamically render the <Route />'s.

And now you have a self organizing, modularized React application.

But wait, there's more!

Adding New Modules

We left out one critical module for our bug tracker: Bugs.

The beauty of our new structure is that all I have to do is add a new module to the export list.

src\modules\Bugs\index.js

import React from 'react';

const Bugs = () => (
    <div>Bugs Module</div>
);

export default {
    routeProps: {
        path: '/bugs',
        component: Bugs,
    },
    name: 'Bugs',
};

src\modules\index.js

import Analytics from './Analytics';
import Bugs from './Bugs'; // added
import Dashboard from './Dashboard';
import Games from './Games';
import Users from './Users';

export default [
    Dashboard,
    Games,
    Bugs, // added
    Users,
    Analytics,
];

Conclusion

I've been using this structure for a couple of years now and love it. It's expanded quite a bit in our application, but I wanted to keep this post simple.

Also I can't take credit for this. When starting out a couple of years ago with React, I was lucky enough to work with a senior React pro. He taught me this structure (and continues to teach me good React practices). I love learning things from other developers!

Thoughts, questions, suggestions? How do you organize your React projects?

Posted on by:

Discussion

markdown guide
 

Thanks for sharing your knowledge, Jack! This is exactly what I was looking for.

Questions:

  • How do you handle state management in your app?
  • How do you handle unit testing? Where do tests fit into this structure?
  • What were some pitfalls you encountered with this approach?

I'm currently trying to design a fairly complex SPA with React-Redux and your answers to above questions would really help me out.

Thanks!

 

Sorry for the late reply! I thought I responded to this but I never did.

State management
Let me tell you, we went through some growing pains here. First, EVERYTHING WAS IN REDUX.

Redux all the things

Then we realized that 80% of our code could use local state, and that global state should only be used for truly global state-y things (app context, user context, etc...). We refactored redux out of most of our components where it made sense, and kept it in the few that needed it (we eventually completely removed redux, and moved to context).

Testing
It's been awhile now since I've been on that project, but if I remember correctly we used snapshot testing. Nothing real fancy here.

Pitfalls
So the other side of this "let the modules organize themselves" coin, is "where is this route, how is this rendered". Typically, all routes are in one place. You lose that with this approach.

 

Hey, Jack!

Super nice article. Congrats. Succinct but well explained how decoupling should work in an application design.

The question I came up after reading was. You've exemplified how a module API exports its entry point, or the "main" component. However, a module can be built by smaller and isolated components. For instance, the Dashboard module may be an aggregation of a component exported by Games and Users module. If we agree on that, how would you suggest a Module exports its smaller components? Via the entry point API or directly from the inner component path?

Best! :)

 

Sorry for the delay, I took a small hiatus after writing this.

That is a good question.

On the one hand, I like to keep private sub-components of a module in the ModuleName\components folder.

But once a component becomes used in multiple places, I usually pull it up to the root components\ folder (and import from my "Components/MyComponent" alias).

Another option would be to change the signature of the modules export to have a default (which would be our contract) and named exports for internal component sharing.

 

i Like your app and i was looking for example Modules architect on react js , with redux and api ...
i know that vue js provide that and easy, by using require-context .
can i get any project example for modules architect on react js on github ? because i like to learn morre .
and also i like to ask about is that possible to make component for example _Errror.js Component , i can use it any where without call it by using import Erreur from .... ? is that possible on react js

 

I'm not aware of anything specifically on the react js github repo about modularizing your react components, but I've linked my example repo in the post. Hope it helps!

 

So i learnt everything from reactjs.org/docs , and yet i was strugling to start a new project , what i truely needed that isn't in the docs is your Great amazing lovely Tut , thank you so much :) i suggest you propose it in react doc github

 

Thank you for the kind words!

 

Wow, can't believe I just stumbled on to this. Thanks, thanks a lot... do you know how long it will take me to redo the front end piece to my app???? Seriously, it's brilliant. Like Django's individual apps in one project.

 

You are king my friend.

It took me 4 hours to find this ......but lordy am i happy.

Question: I see alot of people doing routes on the backend with Express, are you saying that is not necessary with this method?

Thank you!

 

Thanks!

It isn't with this method. This method assumes full SPA with routing handled on the front-end.

The backend of my example app is actually ASP.NET Core. Some different things may have to happen with some kind of express or .net core server-side rendering / handling of routes.

 

Hi Jack,

Thanks for the paper.

I do you do to manage store subscription (in my case, i use redux), action sharing, reusing behaviors between modules and all those React things ?

 

Sorry for the delay, I took a small hiatus after writing this.

Originally, in our app, we used plain old redux. It was a mess.

Then, on every screen, we asked ourselves:

Does this REALLY NEED to be in redux for global state?

The answer, 95% of the time, was no. We refactored out redux and went with Context for truly global state (App state, user state, etc...).

But, to answer your question in 2020 of how I would do it (managing actions / reducers):

For actions / reducers
Use Redux Toolkit or something similar (redux-toolkit.js.org/). Creating all those actions and reducers and managing them was a nightmare.

For shared behavior
Hooks are exactly what that was made for. Got a bug service? useBugs() might return { createBug, searchBugs }. Import and use anywhere.

 
 

Hi @jack , how can one code-split the routing using this modular method, thanks in advance

 

Good question!

We used github.com/jamiebuilds/react-loadable for code-splitting.

Here is an example of the Dashboard modules index.jsx:

import React from 'react';
import Loadable from 'react-loadable';
import DashboardIcon from 'Components/shared/Icons/DashboardIcon';
import ScreenLoader from 'Components/shared/ScreenLoader';

const Dashboard = Loadable({
  loader: () => import('./Dashboard'),
  loading: ScreenLoader
});

export default {
  key: 'dashboard',
  routeProps: {
    path: '/dashboard',
    component: Dashboard
  },
  navigation: {
    permission: [],
    icon: (active, theme) => (
      <DashboardIcon
        style={{
          color: active ? theme.palette.dashboard[400] : theme.palette.contrast.low,
          width: 26,
          height: 26
        }}
      />
    ),
    label: 'Dashboard',
    link: '/dashboard',
    theme: 'dashboard',
  }
};