Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
February 25, 2022 11:55 am GMT

Screaming Architecture - Evolution of a React folder structure

React folder structures... a topic that has been around for ages. But still Reacts unopinionated approach frequently raises questions: Where should I put my files? How should I organize my code? Honestly, even with years of experience, I found myself asking the same questions.

So I went out to get an overview of the most popular approaches to organizing React projects. Based on my research these are:

  • grouping by file type (e.g. separate folders for components, contexts, hooks)
  • grouping by pages with global folders for contexts, hooks, etc
  • grouping by pages with colocation of related components, contexts, and hooks
  • grouping by features.

This write-up reflects my observations of these folder structures evolving in a growing codebase and the problems they can cause. It also includes a short list of best practices and a challenge to turn a design from my upcoming course into a feature-based folder structure.

We won't lay out every detail but rather take a big-picture perspective. In other terms: Where we put our App.js file is less important than the overall approach to organizing files.

To juice this story up well follow the (slightly satiric) journey of a new startup through different stages and a growing codebase. The ingenious idea: Well build the next todo app!

Table of Contents

  1. Prototype: Group by file types
  2. Investment: More files nesting
  3. Growth: We need pages
  4. World Domination: Colocation
  5. Exit: Group by Features
  6. Discussion of the feature-driven folder structure
  7. Best Practices
    • Absolute imports
    • index.js as public API
    • kebab-case for file and folder names
  8. Challenge: How would you structure a project based on this design?

Prototype: Group by file types

Obviously, we have a great vision for our startup. Disruption, conquering the world, you know the drill. But everyone has to start small.

So we begin with the React docs. We read that we shouldnt spend more than 5 minutes deciding on a folder structure. OK, so lets quickly take inventory:

As the first version of our todo startup, a simple list of todo items would do. That should get us some early pre-seed investment, don't you think?

The simplest folder structure for this case seems to be the group files by their types option mentioned in the React docs. This makes our lives easy: Components go in the components folder, hooks in the hooks folder, and contexts in the contexts folder. And since were not cavemen we create a folder per component that contains styles, tests, and what not as well.

 src/     components/          # I'm omitting the files inside most folders for readability        button/        card/        checkbox/        footer/        header/        todo-item/        todo-list/            todo-list.component.js            todo-list.test.js     contexts/          # no idea what this does but I couldn't leave this folder empty        todo-list.context.js     hooks/           # again no idea what this does but I couldn't leave this folder empty         use-todo-list.js

This looks pretty simple. And for someone new to programming, this is a great and uncomplicated way to get started. No need to overthink it.

But as you can guess it won't stay this simple for long.

Investment: More files nesting

Our todo app works great but were running out of money. Its time to get investors on board! Which means we need to show progress. And the best way to show progress is adding new features, right?

Geniuses that we are we have an idea: Why not support editing of todo items? Awesome! We just need a form to edit the todos and maybe a modal to display the form.

 src/     components/        button/        card/        checkbox/          # this modal shows a form to edit a todo item        edit-todo-modal/        footer/        header/        modal/        text-field/          # here is the form that is shown by the modal        todo-form/        todo-item/          # the edit modal is shown on top of the todo list        todo-list/            todo-list.component.js            todo-list.test.js     contexts/        modal.context.js        todo-list.context.js     hooks/         use-modal.js         use-todo-form.js         use-todo-list.js

Not too bad but the components folder is getting crowded. Its also slightly annoying that related folders like checkbox and text-field (both form fields) or edit-todo-modal and todo-form (parent and child) are so far apart.

Maybe we could group and colocate components?

 src/     components/        edit-todo-modal/           edit-todo-modal.component.js           edit-todo-modal.test.js             # colocate -> todo-form is only used by edit-todo-modal           todo-form.component.js           todo-form.test.js        todo-list/             # colocate -> todo-item is only used by todo-list           todo-item.component.js           todo-list.component.js           todo-list.test.js          # group simple ui components in one folder        ui/            button/            card/            checkbox/            footer/            header/            modal/            text-field/     contexts/        modal.context.js        todo-list.context.js     hooks/         use-modal.js         use-todo-form.js         use-todo-list.js

With this folder structure, its easier to get an overview of the important functionality. We removed clutter from the components folder in two ways:

  1. By colocating child components with their parents.
  2. By grouping the generic UI and layout components in the ui folder.

The cleaner structure becomes apparent when we collapse the folders:

 src/     components/        edit-todo-modal/        todo-list/        ui/     contexts/     hooks/

Growth: We need pages

Our startup continues to grow. We launched the app to the public and have a handful of users. Of course, they start complaining right away. Most importantly:

Our users want to create their own todo items!

With a bit of thinking, we find a simple solution: we add a second page where users can create todos via a form. Luckily we can reuse the form for editing todos. Thats amazing because it saves precious resources of our developer team.

Anyway, having custom todo items means we need a user entity and authentication. Since the todo form will now be shared between the create todo page and the edit todo modal we should move it up to the components folder again.

 src/     components/          # we now have multiple pages        create-todo-page/        edit-todo-modal/        login-page/          # this is where the todo-list is now shown        home-page/        signup-page/          # the form is now shared between create page and edit modal        todo-form/        todo-list/           todo-item.component.js           todo-list.component.js           todo-list.test.js        ui/     contexts/        modal.context.js        todo-list.context.js     hooks/           # handles the authorization         use-auth.js         use-modal.js         use-todo-form.js         use-todo-list.js

What do you think about the folder structure now? I see a few problems.

First, the components folder is getting crowded again. But admittedly, we wont be able to avoid this in the long run. At least if we want to keep our folder structure somewhat flat. So let's disregard this problem.

Second (and more importantly), the components folder contains a mixture of different kinds of components:

  • pages (which are entry points to the app and thus important for new devs to understand the codebase)
  • complex components with potential side effects (e.g. the forms)
  • and simple UI components like a button.

The solution: We create a separate pages folder. We move all the page components and their children there. Only components that are shown on multiple pages stay in the components folder.

 src/     components/          # the form is shown on the home and create todo page        todo-form/          # we could also ungroup this folder to make the components folder flat        ui/     contexts/        modal.context.js        todo-list.context.js     hooks/        use-auth.js        use-modal.js        use-todo-form.js        use-todo-list.js     pages/         create-todo/         home/            home-page.js              # colocate -> the edit modal is only used on the home page            edit-todo-modal/            todo-list/                todo-item.component.js                todo-list.component.js                todo-list.test.js         login/           # don't forget the legal stuff :)         privacy/         signup/         terms/

To me, this looks much cleaner. When a new developer joins the company its now easy for them to identify all the pages. This gives them an entry point to investigate the codebase or debug the application.

This seems to be a popular folder structure that many developers use. Here are two examples:

But since the goal of our startup is to conquer the world we obviously can't just stop here.

World Domination: Colocation

Weve grown into a serious business. The worlds most popular todo app (according to its 5-star rating). Everyone wants to pour money into our startup. Our team grows and with it our codebase.

 src/     components/     contexts/        modal.context.js        ...  # imagine more contexts here        todo-list.context.js     hooks/        use-auth.js        use-modal.js        ...  # imagine more hooks here        use-todo-form.js        use-todo-list.js     pages/

Sorry, I ran out of creativity. You get the point: The global hooks and contexts folders get crowded.

At the same time, the code for the more complex components is still scattered over multiple folders. The component may live somewhere in the pages folder, using a shared component in the components folder and relying on business logic in the contexts and hooks folders. With a growing codebase, this makes it a lot harder to track down dependencies between files and promotes intertwined code.

Our solution: colocation! Whenever possible we move the contexts and hooks next to the components where they are used.

 src/     components/        todo-form/        ui/     hooks/          # not much left in the global hooks folder        use-auth.js     pages/         create-todo/         home/            home-page.js            edit-todo-modal/            todo-list/                todo-item.component.js                todo-list.component.js                todo-list.context.js                todo-list.test.js                  # colocate -> this hook is only used by the todo-list component                use-todo-list.js         login/         privacy/         signup/         terms/

We got rid of the global contexts folder. Unfortunately, theres no good place to put the use-auth file so the global hooks folder stays for now. No drama, but the fewer global folders the better. They quickly turn into a dumping ground.

The most important advantage of this folder structure: We can grasp all the files that belong to a feature at once. No need to look into 5 different folders to find the code for a single component.

But at the same time, there are still some problems:

  1. The code related to the todo entity is spread over multiple folders. Which will become a bit messy once we start adding more entities.
  2. Would you guess that the todo-list component lives in the home folder just from looking at the folder structure?
 src/     components/     hooks/     pages/         create-todo/         home/         login/         privacy/         signup/         terms/

Newsletter signup

Exit: Group by Features

Our dreams come true: were about to sell our startup for billions. We created a unicorn FAANGT.

But with success comes responsibility: our users demand new features. Again. Most importantly they want to create different projects to keep their todo items for work separate from the todo items on their grocery list. Who could have guessed...

Our solution: we add a new project entity that contains a list of todo items.

We decide to add two new pages. One to create a project and one to show the project including its todos. The home page has to change as well. It should show a list of all projects as well as a list of all todos.

That means the todo-list component is now used on two pages so it has to move to the common components folder

 src/     components/        todo-form/          # is now shared between home and project page        todo-list/           todo-item.component.js           todo-list.component.js           todo-list.context.js           todo-list.test.js           use-todo-list.js        ui/     pages/         create-project/         create-todo/           # shows now a list of projects and an overview of all todos         home/            index.js            edit-todo-modal/            project-list/         login/         privacy/           # shows a list of todos belonging to a project         project/         signup/         terms/

This still looks quite clean. But I can see two problems:

  • Looking at the pages folder its not immediately clear that this app has todos, projects, and users. We can understand it but we first need to process folder names like create-todo (todo entity) or login (user entity) and separate them from the unimportant stuff (e.g. privacy and terms).
  • It feels arbitrary that some components exist in the shared components folder just because they are used on multiple pages. You need to know where and in how many places a component is used to understand in which folder you can find it.

At this point you might think: you can simply open a file by its name with the help of your IDE (e.g. by hitting Ctrl + P in VS Code). True. But that doesn't help much if you don't remember the name in the first place. So from my perspective, it's always good if you can navigate a codebase in multiple ways.

Lets adjust the folder structure one last time and group our files by feature.

Feature is a pretty broad term and youre free to choose whatever that means to you. In this case, we go for a combination of entities (todo, project, and user) as well as a ui folder for components like buttons, form fields, and so on.

 src/     features/          # the todo "feature" contains everything related to todos        todos/             # this is used to export the relevant modules aka the public API (more on that in a bit)           index.js           create-todo-form/           edit-todo-modal/           todo-form/           todo-list/                 # the public API of the component (exports the todo-list component and hook)               index.js               todo-item.component.js               todo-list.component.js               todo-list.context.js               todo-list.test.js               use-todo-list.js        projects/           index.js           create-project-form/           project-list/        ui/           index.js           button/           card/           checkbox/           header/           footer/           modal/           text-field/        users/            index.js            login/            signup/            use-auth.js     pages/           # all that's left in the pages folder are simple JS files           # each file represents a page (like Next.js)         create-project.js         create-todo.js         index.js         login.js         privacy.js         project.js         signup.js         terms.js

Note that we introduced index.js files to each folder. These are often referred to as the public API of a module or a component. If you dont know what that means you can find a more detailed explanation further below.

But first, lets discuss the new group by features folder structure.

Discussion: Feature-Driven Folder Structure and Screaming Architecture

In his article Screaming Architecture Bob Martin says:

Your architectures should tell readers about the system, not about the frameworks you used in your system. If you are building a health-care system, then when new programmers look at the source repository, their first impression should be: Oh, this is a heath-care system.

Lets remember our initial folder structure where we grouped our files by type:

 src/     components/     contexts/     hooks/

Does this tell us something about the system or the framework? This folder structure screams: Im a React app.

What about our final feature-driven folder structure?

 src/     features/        todos/        projects/        ui/        users/     pages/         create-project.js         create-todo.js         index.js         login.js         privacy.js         project.js         signup.js         terms.js

We have no idea which framework was used. But this folder structure jumps at you and screams Hey, I'm a project management tool.

Thats looks pretty much like what Uncle Bob describes.

Apart from the descriptive architecture, the features and pages give a developer two different entry points to the application.

  • If we need to change a component and only know that its on the home page open pages/home.js and click through the references.
  • If we need to change the TodoList but dont know where its used we simply open the features/todo folder and well find it somewhere inside.

And finally, we got rid of the global contexts and hooks folders. We can still re-introduce them if necessary. But at least for the moment, we removed these potential dumping grounds.

I personally am very happy with this folder structure. We could continue for a bit and clean up the folder structure within a feature. For example, the todo folder currently looks a bit messy. Alan Alickovic with his awesome example project Bulletproof React suggests separating the files inside each feature by file type (as we did in the beginning).

But from my perspective, our current folder structure is sufficiently clean and descriptive. Due to the self-contained nature of the features it should be easy to refactor if necessary. At the same time, our folder structure is simple enough to use in a project from the start. It may save us some headaches in the long run.

From my experience, many projects evolve in a similar way as described on this page. But due to time pressure, the developers never have the chance to clean up the folder structure. So the project ends up in a mess of different approaches. Starting with a feature-driven folder structure can help keeping the app clean over the long run.

If you'd like to take a deep dive into the feature-driven folder structure here is a list of more resources:

Newsletter signup

Best practices

Absolute imports

Let's say we want to render a button in the todo list component inside the file features/todo/todo-list. By default we would use a relative import:

import { Button } from "../../ui/button";...

Managing the relative paths with ../.. can become annoying especially during refactoring sessions when you move files around. It also quickly turns into guesswork to figure out how many .. are required.

As an alternative, we can use absolute imports.

import { Button } from "@features/ui/button";...

Now it doesn't matter where you move the TodoList component. The import path will always be the same.

With Create React App absolute imports are very easy to set up. You just add a jsconfig.json file (or tsconfig.json for TypeScript) and define the paths aliases:

{  "compilerOptions": {    "baseUrl": ".",    "paths": {      "@features/*": ["src/features/*"],    }  }}

You can find more detailed walkthroughs here for React and here for Next.js.

index.js as public API

In our final folder structure, we added an index.js to each feature and component folder. Here a quick reminder:

 src/     features/        todos/             # this is used to export the relevant modules aka the public API           index.js           create-todo-form/           edit-todo-modal/           todo-form/           todo-list/                 # the public API of the component (exports the todo-list component and hook)               index.js               todo-item.component.js               todo-list.component.js               todo-list.context.js               todo-list.test.js               use-todo-list.js        projects/        ui/        users/     pages/  

As mentioned, these index.js files are often referred to as the public API of a module or a component.

But what does that mean?

Here is an example of how the index file in the folder features/todo/todo-list might look like:

import { TodoList } from "./todo-list.component";import { useTodoList } from "./use-todo-list";export { TodoList, useTodoList };

The file simply imports and exports some modules. Here is an even shorter version:

export { TodoList } from "./todo-list.component";export { useTodoList } from "./use-todo-list";

And the file feature/todo/index.js just exports everything from its subfolders.

export * from "./create-todo-form";export * from "./todo-list";// ... and so on

How does that help us?

Imagine you want to render the TodoList component inside the file pages/home. Instead of importing from the nested folder like this

import { TodoList } from "@features/todo/todo-list/todo-list.component";...

we can simply import from the todo feature directly.

import { TodoList } from "@features/todo";...

This has a few benefits:

  1. It looks nicer.
  2. A developer doesn't need to know the inner folder structure of a feature to use one of its components.
  3. You can define which components etc. you want to expose to the outside. Only the things you export in your index files should be used in other parts of the app. The rest is internal/private. Hence the name public API.
  4. You can move around, rename, or refactor everything inside a feature folder as long as the public API stays the same.

kebab-case for file and folder names

Like many others, I used to name component files with PascalCase (e.g. MyComponent.js) and functions/hooks with camelCase (e.g. useMyHook.js).

Until I switched to a MacBook.

During a refactoring session, I renamed a component file called myComponent.js to the correct format MyComponent.js. Everything worked locally but for some reason, the CI on GitHub started complaining. It claimed that the import statement below was broken.

import MyComponent from "./MyComponent";

Turns out, MacOS is a case-insensitive file system by default. MyComponent.js and myComponent.js are the same thing. So Git never picked up the change in file name. Unfortunately, the CI on GitHub used a Linux image. And this one is case-sensitive. So according to my CI the file didnt exist while my local machine said everything was OK.

It took me hours to understand this. And apparently, I'm not the only one who ran into this problem:

kent-c-dodds-kebab-case.png

The solution: use kebab-case for your file and folder names. For example:

  • Instead of MyComponent.js write my-component.js.
  • Instead of useMyHook.js write use-my-hook.js.

This is what Next.js uses by default. Angular included it in its coding styleguide. I don't see a reason why not to use kebab-case but it might save you or a teammate of yours some headaches.

Challenge: How would you structure a project based on this design?

This is a design of an error logging tool for web apps (e.g. like Sentry) from my upcoming course.

prolog-desktop.jpg

  • The entity at the foundation of this app is an organization.
  • Each organization has projects and users assigned to it.
  • Each project has issues (e.g. errors that are sent from an organizations website).
  • Each of the top items in the left navigation represents a page.

How would you turn this design into a feature-based folder structure? (You can find my solution below. Dont peak.)

...
...
...
...
...
...

 src/     features/        alerts/        issues/          # this contains the settings        organization/        projects/           index.js           project-card.js           project-list.js        ui/           index.js           card/           header/           footer/           side-navigation/           tag/        users/     pages/         alerts.js         issues.js         projects.js         settings.js         users.js

Original Link: https://dev.to/profydev/screaming-architecture-evolution-of-a-react-folder-structure-4g25

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To