Organizing Files in a Hooks World
Photo by Jeremy Bishop
How I prefer to organize my react projects and why.
Overview
In every project you have to decide how you want to organize your code. This is important to enable developers to easily navigate the project and quickly orient themselves within a codebase. A large portion of code organization is simply what files go where. I'd like to take a few minutes to discuss my preferences around file location and directory structure and why I have these opinions.
Directory structure strategies
In the React Docs it talks about two different ways of organizing your files. Grouping by features or routes and Grouping by file type.
Grouping by features or routes
Here is an example of how this method looks (Taken straight from the react docs):
1common/2 Avatar.js3 Avatar.css4 APIUtils.js5 APIUtils.test.js6feed/7 index.js8 Feed.js9 Feed.css10 FeedStory.js11 FeedStory.test.js12 FeedAPI.js13profile/14 index.js15 Profile.js16 ProfileHeader.js17 ProfileHeader.css18 ProfileAPI.js
The advantage of this approach is that it mirrors conceptually what a user is trying to accomplish within the app more directly. This is useful since as you're adding or changing code its easy to conceptualize the mapping of where code should "live" to the user interaction you want to add/change/fix.
The downside of this approach is that it can involve more maintenance than other strategies. Imagine that you have a Profile directory for the Profile route and you get a requirement asking you to make the profile route part of the feed. How would our code organization above need to change?
- Move the files under feed Step
- Update all imports relative to the files that were moved.
While this doesn't seem like a big deal as your project grows this can become time intensive and cause your teams velocity to slow down.
Grouping by file type
Here is how this example looks (again taken from the react docs):
1api/2 APIUtils.js3 APIUtils.test.js4 ProfileAPI.js5 UserAPI.js6components/7 Avatar.js8 Avatar.css9 Feed.js10 Feed.css11 FeedStory.js12 FeedStory.test.js13 Profile.js14 ProfileHeader.js15 ProfileHeader.css
The advantage of this approach is that it is more flexible to change so as you receive requirements that would normally cause changes to where files are located because there is literally no mapping of components to where they live in the application you dont have to change anything about file location! 🥳
The disadvantage is just the other the coin. There is no mapping so it takes more grepping to figure out what is used where and it takes longer to get to know the code base because there is no explicit mapping of what part of the app each component controls in the directory structure.
How hooks changes this
In react applications there are 3 main concerns that I have found make sense as their own modules.
- What html is rendered (Components)
- Making API calls
- Client side logic
Making API Calls
Lets take a look at Making API Calls. This concern is designed to be more global
and sharable. These modules you are thinking about how to design your module so
that it exposes a nice API for your consumers the components. I have called
these services
files and they typically expose functions for working with REST
and GraphQL api's.
Client side logic
Client side logic is also designed to be more global and shareable. When React came up with the concept of hooks they were trying to solve the problem of "How do we share logic around manipulating data that doesn't necessarily involve how html is rendered?" This points us to this being at least somewhat global conceptually.
Components
Components are what tie all these pieces together. Components are ultimately responsible for calling services and hooks where they are needed and using their data to render the html.
My Preferences
My preferences are as follows:
1common/2 __test__/3 api-utils.test.ts4 api-utils.ts5services/6 user.ts7 profile.ts8hooks/9 user.ts10 profile.ts11feed-story/12 __test__/13 feed-story.test.tsx14 feed-story.tsx15 feed.tsx16 feed.css17 avatar.tsx18 avatar.css19profile/20 profile-header.tsx21 profile-header.css22 profile.tsx
How this helps
In this example feed-story
and profile
are the two activities conceptually
that a user would perform within the application. Having this mapping exist
helps us more quickly navigate the project. However by making the mapping go
from the route to the job the user is hoping to accomplish decouples it from
routing or view location making it more impervious to changes thus less
maintenance.
One other thing that I've done is that services
and hooks
are at the root of
the project as conceptually they are global things that should be shareable and
putting them there indicates that they can and should be used in any other
component. This also helps as you break logic out of existing components to be
able to put them in one central place which makes them easier to find when you
are wondering if a custom hook has already been written to help you out in your
next component.
services are nice to have at the top level because in most cases they'll be used inside your custom hooks and having them close by helps keep your imports clean and easier to see what services are and are not available for you to use.
Hope this helps you in your projects. I'd love to hear any feedback you might have on this!