DEV Community

Sergio Daniel Xalambrí
Sergio Daniel Xalambrí

Posted on • Originally published at sergiodxa.com

How I Organize React Applications

When using React one of the most common question is how to organize the files inside it. In my years using it I have tried multiple options, including using feature folders, folders per components, one component per file and more, after trying them all, I finally found one that is simple and scale with big projects at the same time.

src/components/
  {name}.tsx
  {name}.test.tsx
src/hooks
  {name}.ts
  {name}.test.tsx
src/routes/
  {name}.tsx
  {name}.test.tsx
src/utils
  {name}.ts
  {name}.test.ts
src/index.tsx               # The entry point
Enter fullscreen mode Exit fullscreen mode

🧱 Components

The first folder is the components folder, this one contains the components of the application. One special thing I do that most React developers don't is that I don't create a new file per React component, this means in the same file I can have more than one React component, in a normal file inside components I wrote things like:

import * as React from "react";
// Possible more imports here

function ListItem(props) { ... }

function List(props) { ... }

function Group(props) { ... }

function Button(props) { ... }

export default function MyComponent() { ... }
Enter fullscreen mode Exit fullscreen mode

This way, all the component I create are in the same file, this make it easier to change them, this doesn't mean all the components are always in the same file, I move components to a new file in some cases

  • I want to lazily import them, in that case I need a new file to import
  • The component is used by another component in a different file
  • The component is used by two or more routes
  • The component is too complex and it makes sense to test it in isolation
  • The component represents a whole feature (usualy this is related to the point above)

And I create a new component inside the same file if

  • I need to re-use it inside the same file
  • I need to use it inside a list and call hooks per each item
  • I want to use hooks only related for that part of the main feature of the file
  • I want to suspend the component, because I use suspense for data fetching, without suspending the parent component
  • The component its getting to big and it makes sense to split it to make it easy to read it

For the tests, I create a single test file per component and put all the tests I wrote there, to write them I use React Testing Library and I follow all the best practices they recommend.

⚓️ Hooks

I create custom hooks everytime I need to share behavior between components and when I'm using SWR to wrap it in hooks specific for each resource of my API.

This way I end up with hooks like useCurrentUser or useTodos, that use SWR internally to fetch data and define the key and fetcher function inside the same file, doing this I can easily share data between components because I can add

import { useCurrentUser } from "hooks/use-current-user";

function MyComponent() {
  const { data: currentUser } = useCurrentUser();
  // the rest of the code
}
Enter fullscreen mode Exit fullscreen mode

Because SWR dedupes requests I don't need to care about using fetching many times because I call it many times, this let me avoid using Context.

And talking about context, I still use it, but only when the value stored in the context is mostly static, e.g. feature flags or assets), this way I don't have issues because the context value changed and triggered a re-render in most of the application.

As with components, I test the hooks, to do it I create a simple Tester component using the hook and I test the component.

I only avoid testing the hook when they are wrappers of SWR.

🗺️ Routes

Note: When using Next.js this is replaced by the pages folder.

Routes is a special components folder, this follow the same rules I use for components inside src/components, the only difference is that they represent a single route of my application.

The files here must always have a single export default, this is required to be able to lazily import the routes in the entry point which is where I define my routes.

These components are sometimes not importing a single component from the components folder, because they implement the whole features the page needs, and when I import external components I try to do it lazily too.

Here I also add tests, but in this case I do more integration tests rather and unit tests, this is because I'm testing the whole page with all the features inside it.

🔨 Utils

I create utility functions all the time, it helps me name piece of logics or simple make it easier to understand what is happening, specially when there are multiple conditions because I can do things like:

function getSomething() {
  if (condition) return value;
  if (anotherCondition) return anotherValue;
  return yetAnotherValue;
}
Enter fullscreen mode Exit fullscreen mode

However, I don't always create them in a file inside src/utils, I first write them inside the same file it needs them, this could be a hook, component, route, or even another utility, but if my utility function is used in three or more files then I move it to a file inside the utils folder to avoid duplicating it (WET).

I also write test for the utils I code only when they are large or complex enough to need it, and when they are not only wrappers of another function, specially if they wrap a browser API.

🚪 Entry Point

Finally, the entry point is where I lazily import all the routes, I import the context providers I may need, and I define all my routes and render the whole application.

I don't really create an App component because most of the time I can directly render the Router and Route components without wrapping them in an App.

When using Next.js, this is the pages/_app.tsx file.

🖼️ Appendix: Assets

When using assets, I don't like to import them in my code, this makes the build process way slower and the only benefit, have hashes in the assets name, I can gain it using other specialized tools. In my case I work with Rails as a backend, thus I let Rails handle static assets and I use the Rails view to pass the URLs of those assets to React adding a script of type application/json with a JSON containing the URLs.

<script type="application/json" id="initial-props">
{
  "assets": {
    "logo": <%= asset_path("images/logo.png") %>
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)