loading...
Cover image for How should we structure our React code? (2/2)

How should we structure our React code? (2/2)

surajjadhav profile image Suraj Jadhav ・9 min read

Note: This post doesn't portray that this is the only way to structure the code. There are many other great ways to do it. This method might have already been followed by most of you. This post also considers that you have some background knowledge about React and its ecosystem, also some common terminologies used in the web community

This is the second post of two post series. If you haven't read PART-1 yet then go ahead.

I apologize for being late to post this. I binge-watched The Office series and wasn't able to resist to finish it before writing this down.

Let's continue our journey to learn how to build a well-defined file structure for your Reac SPA or any of your favorite web-stack.

Continued...

So if you remember this is what our App component looks like,

import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";
import './App.css';
import routeConstants from 'shared/constants/routes';

const {
  LOGIN,
  DASHBOARD,
  LISTING,
  PROFILE,
} = routeConstants;

function App() {
  return (
    <Router>
      <div className="App">
        <h1>App Component</h1>
        <ul className="App-nav-list">
          <li className="App-nav-item">
            <Link to={LOGIN.route}>{LOGIN.name}</Link>
          </li>
          <li className="App-nav-item">
            <Link to={DASHBOARD.route}>{DASHBOARD.name}</Link>
          </li>
          <li className="App-nav-item">
            <Link to={LISTING.route}>{LISTING.name}</Link>
          </li>
          <li className="App-nav-item">
            <Link to={PROFILE.route}>{PROFILE.name}</Link>
          </li>
        </ul>
        <Switch>
          <Route exact path={LOGIN.route}>
            <h1>{LOGIN.name}</h1>
          </Route>
          <Route path={DASHBOARD.route}>
            <h1>{DASHBOARD.name}</h1>
          </Route>
          <Route path={LISTING.route}>
            <h1>{LISTING.name}</h1>
          </Route>
          <Route path={PROFILE.route}>
            <h1>{PROFILE.name}</h1>
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

export default App;

I will create a Navbar component that will render the nav links in our App component. So let's create a file Navbar.js in src/screens/App/components directory.

Code-File-1

Also making subsequent changes in App component file as well to import Navbar and use it,

// Other import statements

import Navbar from "./Navbar";

const {
  LOGIN,
  DASHBOARD,
  LISTING,
  PROFILE,
} = routeConstants;

const navItems = [LOGIN, DASHBOARD, LISTING, PROFILE];

/* Inside return statement */

        <h1>App Component</h1>
        <Navbar navItems={navItems} />

/* other jsx code */

export default App;

The next thing we should do is give each route component their own space in our file structure. For that, I will create a directory named screens under src/screens/App directory.

I used the word screens here in my file structure is because our Application is a set of things that we see on our screens, it is easier for me or anyone to relate/understand that word. You can use any word you want like routes/children/views

I didn't use views because our views can differ. :P Nope that's not the reason. Maybe because I am taking Micheal Scott a bit too seriously.

File-Structure-1

So now the src/screens/App directory contains two folders components and screens. The newly created screens contain 4 directories for all our routes, the Login, Dashboard, Listing, and Profile routes. Now, let's create a folder named components and a file index.js in each of the newly created folders.

File-Structure-2

Then, I went ahead and create a component file for each route and fill it will a header tag and then exported that component in index.js as given below,

This is the component file contents

import React from 'react';

function Dashboard() {
  return <h1>Dashboard</h1>;
}

export default Dashboard;

This is the index.js to export the above component,

import Dashboard from './components/Dashboard';

export default Dashboard;

I am considering you did this for Login, Listing and Profile too.

Now I will import these route components in the App component and use them in rendering.

/* Other imports */
import Login from '../screens/Login';
import Dashboard from '../screens/Dashboard';
import Listing from '../screens/Listing';
import Profile from '../screens/Profile';

/* Other App component code */

<Route exact path={LOGIN.route}>
    <Login />
</Route>
<Route path={DASHBOARD.route}>
    <Dashboard />
</Route>
<Route path={LISTING.route}>
    <Listing />
</Route>
<Route path={PROFILE.route}>
    <Profile />
</Route>

/* Other code */

export default App;

Okay now let's move router, routes and routing configuration into a separate space of itself. This will make sure our component files remain clean and lean. Okay, both words rhymed. :P

I will create a file route.js in each route folders which will export a route configuration. An example is given below,

Code-File-2

I did the above for other routes and also created a route.js file in the src/screens/App directory to import all those routes as given below.

Code-File-3

The next thing would be to make changes to our App component for incorporating these route config additions.

Just so that you know, I am referring to the react-router-dom DOC for doing all these changes required for the route-config to work.

Moving on, I am going to create a folder in the src/shared directory, which will hold those components that can be shared across all our applications. For now, I am going to add one component in it which will be used for rendering route components.

Code-File-4

This component will receive an object containing the path to route and component to render. Let's export the above-shared component with an index.js file in src/shared/components

export { default as RouteWithSubRoutes } from './RouteWithSubRoutes.js';

export default {};

Next thing, we will make changes in the App and Navbar component. Pasting the code here.

import React from 'react';
import {
  BrowserRouter as Router,
  Switch,
} from 'react-router-dom';

import { RouteWithSubRoutes } from 'shared/components';

import './App.css';
import routes from '../route';
import Navbar from './Navbar';

function App() {
  return (
    <Router>
      <div className="App">
        <h1>App Component</h1>
        <Navbar />
        <Switch>
          {routes.map((route, i) => (
            <RouteWithSubRoutes key={i} {...route} />
          ))}
        </Switch>
      </div>
    </Router>
  );
}

export default App;
import React from 'react';
import { Link } from 'react-router-dom';

import { routeConstants } from 'shared/constants';

const {
    LOGIN,
    DASHBOARD,
    LISTING,
    PROFILE,
} = routeConstants;

const navItems = [LOGIN, DASHBOARD, LISTING, PROFILE];

function Navbar() {
    return <ul className="App-nav-list">
        {
            navItems.map((navItem, i) => (
                <li key={i} className="App-nav-item">
                    <Link to={navItem.route}>{navItem.name}</Link>
                </li>
            ))
        }
    </ul>
}

export default Navbar;

So now the concerns are separated. We import route objects from individual Login, Dashboard, Listing, and Profile directories and form it into a single route array in src/screens/App directory. This way each screen is responsible for its existence.

Now, if you have to change anything about any particular screen or add a new feature to a particular screen, the developer needs to just go to that folder, make changes in that sub-directory and that's it.

Adding a new route

Let's go through the steps to add a new route. We will name it About.

First, we will add a new entry in src/shared/constant/route.js

export default Object.freeze({
    LOGIN: {
        name: 'Login',
        route: '/'
    },
    DASHBOARD: {
        name: 'Dashboard',
        route: '/home'
    },
    LISTING: {
        name: 'Listing',
        route: '/list'
    },
    PROFILE: {
        name: 'Profile',
        route: '/me'
    },
    ABOUT: {
        name: 'About',
        route: '/about'
    }
});

Second, we will make changes to Navbar to add the newly added route. But wait a minute, I don't want to do that. Let's make some changes to the Navbar component so that we don't need to keep changing it next time we add a new route.

import React from 'react';
import { Link } from 'react-router-dom';

import { routeConstants } from 'shared/constants';

const navItems = Object.values(routeConstants);

function Navbar() {
    return <ul className="App-nav-list">
        {
            navItems.map((navItem, i) => (
                <li key={i} className="App-nav-item">
                    <Link to={navItem.route}>{navItem.name}</Link>
                </li>
            ))
        }
    </ul>
}

export default Navbar;

Third, we will create a new folder named About under src/screens/App/screens. Also, quickly add a components folder, index.js, route.js, and add About.js file to the newly created components folder which will contain our route component.

src/screens/App/screens/About/components/About.js

import React from 'react';

function About() {
  return <h1>About</h1>;
}

export default About;

src/screens/App/screens/About/route.js

import { routeConstants } from 'shared/constants';
import About from "./";

export default {
    path: routeConstants.ABOUT.route,
    component: About
};

src/screens/App/screens/About/index.js

import About from './components/About';

export default About;

Lastly, we will need to import and add About route in src/screens/App/route.js

import LoginRoute from "./screens/Login/route";
import DashboardRoute from "./screens/Dashboard/route";
import ListingRoute from "./screens/Listing/route";
import ProfileRoute from "./screens/Profile/route";
import AboutRoute from "./screens/About/route";

export default [
    LoginRoute,
    DashboardRoute,
    ListingRoute,
    ProfileRoute,
    AboutRoute
];

And we are up with a new About route

Sub routes

Let's imagine a scenario where you need to add new sub-routes as child routes to an already existing one. I will pick up the Profile route to do that. We will have an index route Details which shows Profile details and another route Settings to show a list of profile settings.

So localhost:3000/me will render Details and localhost:3000/me/settings will render Settings

First, create a screens folder in Profile sub-directory.

Second, we will add the following detail under PROFILE key in src/shared/constants/route.js

    PROFILE: {
        name: 'Profile',
        route: '/me',
        subroutes: {
            SETTINGS: {
                name: 'Settings',
                route: '/me/settings'
            }
        }
    },

Third, we will create two new folders named Details and Settings under following path src/screens/App/screens/Profile/screens. Also, quickly add a components folder, index.js, route.js, and add the component file to the newly created components folder which will contain our route component. Make sure you perform this for both Details as well as Settings subroutes. Following snippets show what needs to be done.

File content - Details.js

import React from 'react';

function Details() {
  return <h1>Details</h1>;
}

export default Details;

File content - Settings.js

import React from 'react';

function Settings() {
  return <h1>Settings</h1>;
}

export default Settings;

src/screens/App/screens/Profile/screens/Settings/route.js

import { routeConstants } from 'shared/constants';
import Settings from "./";

export default {
    path: routeConstants.PROFILE.subroutes.SETTINGS.route,
    component: Settings
};

src/screens/App/screens/Profile/screens/Details/route.js

import { routeConstants } from 'shared/constants';
import Details from "./";

export default {
    exact: true,
    path: routeConstants.PROFILE.route,
    component: Details
};

src/screens/App/screens/Profile/screens/Settings/index.js

import Settings from './components/Settings';

export default Settings;

Note: Do this with Details subroute too.

Next step would be to update our Profile route config

Code-File-5

Lastly, we will need to add a navigation link to the Profile screen. For that, we will make changes in the Profile component.

src/screens/App/screens/Profile/components/Profile.js

import React from 'react';
import { Switch, Link } from 'react-router-dom';
import { RouteWithSubRoutes } from 'shared/components';
import { routeConstants } from 'shared/constants';

const { PROFILE } = routeConstants;
const { SETTINGS } = PROFILE.subroutes;

function Profile({ routes }) {
  return <>
    <h2>Profile</h2>
    <ul className="App-nav-list">
      <li className="App-nav-item">
        <Link to={PROFILE.route}>Details</Link>
      </li>
      <li className="App-nav-item">
        <Link to={SETTINGS.route}>{SETTINGS.name}</Link>
      </li>
    </ul>
    <Switch>
      {routes.map((route, i) => (
        <RouteWithSubRoutes key={i} {...route} />
      ))}
    </Switch>
  </>;
}

export default Profile;

The File structure looks like this now.

File-Structure-3

I know you guessed it right, it's a fractal structure which I implemented here. It is basically to repeat a structure when you zoom in to a particular folder. Of course, it is meant for the screens directory.

Things that I left

  1. Test files - I have not included writing tests in this post but make sure to keep it as close to the file that you are testing as possible.
  2. CSS - I am a fan of a CSS-preprocessor but you can go ahead and use CSS-IN-JS or JSS which compliments this approach.
  3. A pat on my back for writing this down. :P

Benefits

  1. Few to none git merge conflicts.
  2. Separation of concerns
  3. Easy to grasp at first glance
  4. If you want to take out a particular route and move into its repository it is easy because we have built it to sustain itself.
  5. Scaling is easy as steps to add new route or sub-routes is easy.

Final App,

Alt Text

GitHub logo surajhell88 / react-spa-file-structure

Code for dev.to blog article https://dev.to/surajjadhav/how-should-we-structure-our-react-code-1-2-1ecm

Conclusion

I know both posts were long enough to bore you down. But this is a very important time to invest in the file structure. Because as the product grows, we face more difficulties in managing our code files. We don't want to land on our wrong foot if a new feature compels us to redesign our whole app code.

Make sure each route contains all the code that it needs to run on, also keep an index.js file to export your main code file from a folder. Keep test files as closer to the code as possible. And most importantly do let me know if you have any questions in the comments section below.

Thanks for reading

Posted on by:

surajjadhav profile

Suraj Jadhav

@surajjadhav

I've been a web-dev since last 7 years. Simply love the community. Will try to write about my learnings here.

Discussion

markdown guide
 

Thank you for your post.
I recently created a dashboard with a login and I em thinking about restructuring like you describe.
Could you upload the code for easy reference?

 

This is so silly of me. I will upload it right away and link it here.

 

I have linked the Github repo at the end of the article. Thank you for pointing out this one.

 

Thanks, Just curious, Is this good structure for a E Commerce Application or Dev.to site ?

 

Although, I would recommend you try this as a side project first. I am confident this method should work for any web-stack out there. This file structure is majorly used in Nodejs apps. I read an article and applied it to my React SPA and it worked well for me.

 

Do you recommend to watch the office 😁?

 

You sure should watch it. But do finish up anything you are working on right now and then start the series. Because I was couldn't just stop watching it.