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.
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.
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)
Thanks for sharing your knowledge, Jack! This is exactly what I was looking for.
Questions:
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!
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-...
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.
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.
Thanks, this helped me a lot to restructure my app in better way. Still, got warning:
Because my index.js has
What are your recommendations? Of course I can reconfigure eslint, but is there any other way to define routeProps and name?
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
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:
and the modules/index:
and finally referencing in app like so:
Modules.list
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.
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!
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.
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!
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
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:
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.
You deserve an accolade
How we can manage session or user authorization for this multiple modules****
That's a good idea .... Thanks for sharing