DEV Community

Jack Williams
Jack Williams

Posted on

Organizing Your React App Into Modules

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?

Top comments (23)

Collapse
 
_pankajc profile image
Pankaj

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!

Collapse
 
djmisterjon profile image
DjMisterJon

not big fan of REDUX
but RES are crazy for manage data, and very near of classic vanilla js structure.
All your module can be a store, and will update if they change.
github.com/RisingStack/react-easy-...

Collapse
 
jack profile image
Jack Williams

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.

Collapse
 
fernandosouza profile image
Fernando Souza • Edited

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! :)

Collapse
 
jack profile image
Jack Williams

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.

Collapse
 
teetl6 profile image
teetl6 • Edited

Thanks, this helped me a lot to restructure my app in better way. Still, got warning:

src/modules/Login/index.js
  Line 31:1:  Assign object to a variable before exporting as module default  import/no-anonymous-default-export
Enter fullscreen mode Exit fullscreen mode

Because my index.js has

export default {
  routeProps: {
    path: '/login',
    component: Login
  },
  name: 'Login'
}
Enter fullscreen mode Exit fullscreen mode

What are your recommendations? Of course I can reconfigure eslint, but is there any other way to define routeProps and name?

Collapse
 
zeeglenn profile image
zeeglenn

I know almost noting about react, angular has been my ui platform for a bit, but I was able to remove that lint warning like so:

const Forms = () => (

Forms Module

);

const FormsModule =
{
routeProps: {
path: '/forms',
component: Forms
},
name: 'Forms',
};

export default FormsModule;

and the modules/index:

const Modules = {
name: 'modules',
list: [
FormsModule,
]
}
export default Modules;

and finally referencing in app like so:

Modules.list

Collapse
 
rustaceanx profile image
rustaceanX

Just edit and format your res for easy follow. Nothing special!

I know almost noting about react, angular has been my ui platform for a bit, but I was able to remove that lint warning like so:

const Forms = () => (
    FormsModule
);

const FormsModule = {
    routeProps: {
        path: '/forms',
        component: Forms
    },
    name: 'Forms',
};

export default FormsModule;
Enter fullscreen mode Exit fullscreen mode

and the modules/index:

const Modules = {
    name: 'modules',
    list: [
        FormsModule,
    ]
}

export default Modules;
Enter fullscreen mode Exit fullscreen mode

and finally referencing in app like so:

Modules.list

Collapse
 
snmpboy profile image
Mitch Raful

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.

Collapse
 
aimadghssisse profile image
Aimad Ghssisse

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

Collapse
 
jack profile image
Jack Williams • Edited

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!

Collapse
 
honlordbyronmartinez profile image
Byron Martinez • Edited

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!

Collapse
 
jack profile image
Jack Williams • Edited

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.

Collapse
 
zackheisnberg profile image
zackheisnberg

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

Collapse
 
jack profile image
Jack Williams

Thank you for the kind words!

Collapse
 
leplerjacob profile image
Jacob Lepler • Edited

This is cool. Love learning about new ways to organize a file structure. Right now working on a personal project called WaitLess using class based components. We don't use index.js, but rather .module.js | .style.css | etc...

I was wondering how to incorporate private components only used by one module. Based off your comment @jackjwilliams, I will use another folder called components inside the module folder, and stuff them into there :)

Thank you,
Jacob L
@leplerjacob <<

...btw have you heard of the "Fractal" file structure. I've used that as well

Collapse
 
webpest profile image
Abayomi Oloyinde

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

Collapse
 
jack profile image
Jack Williams

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',
  }
};
Collapse
 
sylvainlg profile image
Sylvain

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 ?

Collapse
 
jack profile image
Jack Williams

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.

Collapse
 
webpest profile image
Abayomi Oloyinde

You deserve an accolade

Collapse
 
pbsonawane profile image
Pravin

How we can manage session or user authorization for this multiple modules****

Collapse
 
raminhbbau profile image
RaminHbb

That's a good idea .... Thanks for sharing