DEV Community

Cover image for How I Organize React Projects
Adam Nathaniel Davis
Adam Nathaniel Davis

Posted on

How I Organize React Projects

I fully understand that talking about the "right" way to organize a React project (or, a project using any framework in any language) is a bit like talking about the "right" way to style your hair. (Although I think we can all agree that the objectively "right" way to style your hair is, quite obviously, in a Mohawk.)

As "basic" as project layout can be, I still find myself - after a quarter century in this game - constantly tweaking and evolving my "default" project structure. So before I dive into more tactical details of my Spotify Toolz project (https://www.spotifytoolz.com), I wanted to crank out a quick article about how I currently organize my React projects.

I also welcome some "audience participation" on this article. Even now, after all this time slinging code, it seems that every six-months-or-so, I come to some stunning realization that "this data should really be stored over there!" So I'd love to see your best practices for how to organize projects.

As with all issues as subjective as "project organization", I can 100% guarantee that my current approach is the empirically best approach. I can also guarantee that any other approach is "wrong". And that, six months from now, I will have adopted an entirely different approach to project organization. At that time, I'll scoff at anyone who follows the organization in this article and I'll dismissively tell them that I've moved on to a far superior organization scheme.

If you use this organization scheme, and you eventually grow unsatisfied with it, I will gleefully offer to refund 150% of the money that you paid to read this article.


Alt Text

Basic Organization

(If you can't fathom what the above image represents, I will try to forgive you. Suffice it so say that it's something akin to a phonograph, or a buggy whip.)

Most of my (latest) React projects have a structure pretty close to this:

/src
  app.js
  /shared
    /classes
    /components
    /css
    /functions
    /hooks
    /objects
      /models
  /routes
Enter fullscreen mode Exit fullscreen mode

If there is any UI aspect to my app at all, I usually assume that I'll be using React Router. And if I'm using React Router, then the /routes directory becomes a one-to-one representation of the (faux) directories that the user sees as they navigate through the app.

So, if the app has a users module (/user), which then has separate "pages" to create (/user-create), edit (/user-edit), and view (/user-view) a user, my project would look like this:

/src
  app.js
  /shared
    /classes
    /components
    /css
    /functions
    /hooks
    /objects
      /models
  /routes
    /user
      /create
      /edit
      /view
Enter fullscreen mode Exit fullscreen mode

Furthermore, when I create components that map to these routes, they're represented by JS files under their appropriate folders. So, once we fill in the base components for each route, the tree looks like this:

/src
  app.js
  /shared
    /classes
    /components
    /css
    /functions
    /hooks
    /objects
      /models
  /routes
    /user
      /create
        /components
          create.user.js
      /edit
        /components
          edit.user.js
      /view
        /components
          view.user.js
Enter fullscreen mode Exit fullscreen mode

Notice that there are no files directly under the /routes/{routeName} folders. This is because everything that defines a route should logically fall under either the classes, components, css, functions, hooks, or objects folders.

On a practical level, this means that most of the logic for my routes are located under /src/routes/{routeName}/components/{route.name.js}. Because, for most of my routes, all of the route-specific logic is encapsulated in /src/routes/{routeName}/components/{route.name.js}.

Now let's imagine that view.user.js (which will be the <ViewUser> component) requires a function that is called getLastUserLoginTimestamp(). When I create that function, I have an organizational choice to make. The choice is determined by this question:

Will this function be used in this component, and only ever in this component??


If this answer is "yes" (i.e., if this function is completely unique and solely targeted to this component), then I would create a structure that looks like this:

/src
  app.js
  /shared
    /classes
    /components
    /css
    /functions
    /hooks
    /objects
      /models
  /routes
    /user
      /create
        /components
          create.user.js
      /edit
        /components
          edit.user.js
      /view
        /components
          view.user.js
        /functions
          get.last.user.login.timestamp.js
Enter fullscreen mode Exit fullscreen mode

In this scenario, I've decided that the getLastUserLoginTimestamp() function will only ever be used in the ViewUser component. For that reason, I created a separate /functions directory under the /src/routes/user/view directory. The implication is that getLastLoginTimestamp() will only ever be used inside the ViewUser component. And thus, the /functions directory that houses the function should only ever live under /src/routes/user/view.

But to be frank, the above example is rare. Typically, when I'm creating helper functions, I already know they'll be used in other places throughout the app. In fact, even if I'm not sure how they will be used throughout the app, I usually assume that the functions I'm creating will, eventually, be used in other places.

For this reason, I rarely house the functions under a specific /src/routes/{routeName} directory. More often than not, I house those functions under the /shared directory. So that would look like this:

/src
  app.js
  /shared
    /classes
    /components
    /css
    /functions
      get.last.user.login.timestamp.js
    /hooks
    /objects
      /models
  /routes
    /user
      /create
        /components
          create.user.js
      /edit
        /components
          edit.user.js
      /view
        /components
          view.user.js
Enter fullscreen mode Exit fullscreen mode



Alt Text

Sharing Is Caring

If it's not clear already, the '/src/shared' directory in my apps houses the lion's share of all my application logic. This happens for two reasons:

  1. Many classes / components / styles / functions / hooks / objects are designed, from the very start, to be "universal". Even if I don't know how a particular file will be re-used in the future, I'm typically writing my files in such a way that I assume they'll be re-used. And thus, most of those files end up being housed under /src/shared.

  2. Even if it seems like a given class / component / style / function / hook / object will only ever be used in a single route, I tend to save the file under /src/shared unless I'm absolutely 100% certain that the file will never possibly be used anywhere else.

This tends to mean that my /src/shared directory is an ever-growing library of potentially-reusable assets. It also means that my /src/routes directories are sparse - but they are a fairly simple one-to-one mapping of the user's potential paths through the application.


Alt Text

Important Notes

At this point, I typically write all of my React components as function-based components. This means that I don't use export class SomeComponent extends React.Component {...}. Instead, I write export const SomeComponent = () => {...}.

So when you look at the directory structure above and you see /src/shared/classes, it might be tempting to think that this directory houses class-based components. But that's not the case.

In my chosen project structure, /src/shared/classes only houses utility helper classes. For example, I frequently use a helper class for localStorage (that you can read about here: https://dev.to/bytebodger/getting-more-out-of-and-into-storage-with-javascript-41li) and a validation library (that you can read about here: https://dev.to/bytebodger/better-typescript-with-javascript-4ke5). This is my only real use of classes in my most-recent React development.

You'll notice that, under /src/shared, there's a /components directory. This isn't for the "main" components that define routes. This is for all of those "helper" components (e.g., Higher Order Components) that I end up using repeatedly throughout my app.

In my particular approach, the /src/shared/css folder typically houses actual CSS classes. If I'm using inline-CSS within my JSX, that's defined in /src/shared/objects (because, with inline CSS, styles are JavaScript objects).

I rarely ever create a Hook that does not live under /src/shared/hooks. In my way of thinking, if your Hook will never be shared between multiple components, then why wouldn't you just define it in the body of the single functional component where it's used??

Finally, my use of /src/objects may be confusing to some. I've found a number of use-cases for "plain ol JS objects" in my dev. You can find one example of that here: https://dev.to/bytebodger/hacking-react-hooks-shared-global-state-553b and here: https://dev.to/bytebodger/why-is-this-an-anti-pattern-in-react-427p

As for my use of /src/objects/models, that's explained with my validation library here: https://dev.to/bytebodger/better-typescript-with-javascript-4ke5 In brief, my /src/objects/models helps me to validate the shape of objects that are being passed into my functions.


Alt Text

Show Me Yours

This is how I currently organize React projects. (Which I'm sure we'll all agree is the right way.) How do you organize your projects? Have I overlooked anything??? Lemme know...

Top comments (3)

Collapse
 
alexandrudanpop profile image
Alexandru-Dan Pop

I for one also like having specific rules for folder structure. Would add a few things that correspond to how I structure code:

  • putting too many things in shared will create a lot of shared files that are actually used only in one area - so probably would put things in a feature folder and only move them to shared when they are actually shared
  • folders that specify the code type (component/class/reducers) are not really helping me explore the code. I might just open folders that have only one file. I instead collocate files - components, hooks, providers, whatever in their feature folder.
  • the main rule for organizing files and folders is by feature
Collapse
 
bytebodger profile image
Adam Nathaniel Davis

Sounds like our approaches aren't terribly different. It just seems like I'm more likely to put things in /shared by default, and you're more likely to put them in /routes (or however you name your "feature" directories) by default. Of course, with modern IDEs, it's usually pretty fast-and-simple to drag items into/out-of shared/features in the event that you feel they belong somewhere else in the future.

Thanks for the feedback!

Collapse
 
fly profile image
joon

Love the assertiveness :)