You have in mind to start a new React app but you have no clue what tools to use and how to use them. Fortunately for you, there's a list of all the best modern React libraries to use (and why) in this article: go check them out!
Once you have choosen all the best tools to build your React project, you need to know how to use them. Knowing how to use them implies knowing how to organize your code so that you naturally enforce the best development practices related to the tools you use.
I like to see the optimal code architecture as the result of the minimization of 3 quantities:
Carefully keeping track of these quantities, there are many reasons why you’d care about how you organize your code:
Alright, let’s not beat around the bush a little more; Below is a file arborescence which will help you enforce the best practices for React and speed up your development by helping you find what you need, where you intend it to be — given the setup you just installed.
👇 You will find more detailed information in the following subsections. Don’t hesitate to check this out again when reading.
❓ You’ll find a repository to generate this kind of architecture and each of its elements at the end of this article!
src/
... components/
... .......... MyAwesomeComponent/
... .......... .................. components/
... .......... .................. MyAwesomeComponent.component.tsx
... .......... .................. MyAwesomeComponent.container.tsx
... .......... .................. MyAwesomeComponent.hooks.ts
... .......... .................. MyAwesomeComponent.style.ts
... .......... .................. index.ts
... modules/
... ....... MyAwesomeModule/
... ....... ............... MyAwesomeModule.actions.ts
... ....... ............... MyAwesomeModule.selectors.ts
... ....... ............... MyAwesomeModule.reducer.ts
... ....... ............... MyAwesomeModule.saga.ts
... ....... ............... MyAwesomeModule.types.d.ts
... ....... modules.d.ts
... ....... reducers.ts
... ....... sagas.ts
... ....... store.ts
... pages/
... ..... MyAwesomePage/
... ..... ............. components/
... ..... ............. pages/
... ..... ............. MyAwesomePage.component.tsx
... ..... ............. MyAwesomePage.container.tsx
... ..... ............. MyAwesomePage.hooks.ts
... ..... ............. MyAwesomePage.style.ts
... ..... ............. index.ts
... services/
... ........ types/
... ........ ..... MyAwesomeService.types.d.ts
... ........ MyAwesomeService.ts
... ........ style.ts
... ........ theme.ts
App.tsx
App.router.tsx
index.tsx
One of the most important React development paradigm is component reusability: you don’t want to rewrite a component’s logic twice — with some modifications indeed — if its purpose can be generalized so that its behavior covers both the use cases.
Once you have created a generalized component, you don’t want to import it from where it was first used/created: it just doesn’t make sense. Imagine someone else trying to find it: that someone else needs to have one specific place where to look for it. This place is indeed the components/
directory; which makes the components stored inside Global Components.
components/
MyAwesomeComponent.component.tsx
contains the component’s rendering logic: what hooks are invoked, how things are displayed and never more (keep things clear and concise)MyAwesomeComponent.container.tsx
(facultative) contains the component’s Higher-Order definition: basically where you connect your component to your store:export default compose(
connect(mapStateToProps, mapDispatchToProps),
React.memo,
)(MyAwesomeComponent);
MyAwesomeComponent.hooks.ts
(facultative) contains the component’s own hooks: instead of creating your form and related submit/handler functions in the functional component’s body, create a specific hook, import it and call itMyAwesomeComponent.style.ts
(facultative): contains the component’s level styled-components: instead of creating your styled components inline just aside your functional component’s body, create it in this file and import itindex.ts
: Used for exporting the component at the directory levelSince you are managing a normalized state in your app, you’ll need somewhere to check how we are handling actions, sagas, how related selectors, types are defined; and you will need this place to be organized as your state is, for simplicity.
The important thing here is that you have distinct action files for actions related to distinct parts of your normalized state, once again to minimize search cost and onboarding cost. This means you need to have distinct Author.actions.ts
and Book.actions.ts
. Indeed, this rule follows for selectors, reducers, sagas and types.
Every module — defined as a part of the state, referred below as the relative state — shall register a subdirectory of modules/
containing :
MyAwesomeModule.actions.ts
: the set of actions responsible for the management of the relative part of the stateMyAwesomeModule.selectors.ts
: the set of selectors allowing you to select each individual part of the relative state (to prevent useless re-rendering), and some additional selectors to aggregate them as needed according to the UIMyAwesomeModule.reducer.ts
: the Redux reducer, responsible for the behavior of the relative stateMyAwesomeModule.saga.ts
: the Redux saga, listening for this module’s actionsMyAwesomeModule.types.d.ts
: the types declaration file associated with the relative state, to use them wherever needed without the need to import themYou’ll note that module/
also contains basic files associated with a React + Redux app, namely:
reducers.ts
: aggregate all the modules’ reducers into one reducersagas.ts
: aggregate all the modules’ sagas into one sagastore.ts
: create, enhance and exports the redux storemodules.d.ts
: redefine the DefaultRootState
type for convenient purposesYour React app will definitely allow the user to navigate between several pages — which are indeed React components — that you don’t want to store inside your components/
folder because you want intuition to guide future developers of your project: where’s the first place you would look at to check a behavior happening on your homepage?
⚠️ Spoiler alert: Under
pages/homepage/
The trick here is to carefully define what a Page is: let’s define it as a unique view, accessible only via a change in the URL.
The main thing to note here is that we have the same arborescence as for the Global Components, plus the recursive definition of the pages/
subdirectory — once again to ensure that when searching for a piece of code, we look at the right place, the first time. Also, you don’t want to use components tied to a specific parent component’s context in other places of your app: that’s why you don’t always declare Global Components and instead declare them in the components/
subdirectory of the parent component/page.
One additional thing to know is that if you define one or more pages under a page, you will need a router to display them correctly: this router should be created as a component of the parent page, and invoked inside the parent page's component:
... pages/
... ..... MyAwesomePage/
... ..... ............. components/
... ..... ............. .......... PageRouter.component.tsx
... ..... ............. .......... -> defines sub-routes to sub-pages
... ..... ............. pages/
... ..... ............. ..... MyAwesomeSubPage/
... ..... ............. ..... -> defines one of the sub pages
... ..... ............. MyAwesomePage.component.tsx
... ..... ............. -> invokes <PageRouter />
AppRouter.tsx
-> defines a route to MyAwesomePage
Basically, this is where all the stuff only used at render time goes: utility functions, rendering constants, business logic, hard computing. Most of the time, specific types come up when writing utility functions or computing: you’ll definitely need a declaration file for these — a specific one, once again to know where to find the related types.
This folder is less governed by rules than the others, because of its more vague definition. However, I recommend that each service — defined as a set of functions used only at render time — register a file of services/
.
Additionally, services/
shall contain a types/
subdirectory, containing each type declaration file for each service.
Finally, when using Material UI and styled-components, you’ll definitely need each of these files:
style.ts
: design constants and functions used throughout the apptheme.ts
: custom Material UI theme exportHave you ever found yourself onboarded on a javascript project?
The first thing you look after in a javascript project is the root index.js
, to lead you into figuring out how the app is working globally — because it’s the very first file javascript will read. On a React TypeScript project, you are basically looking for this file as index.tsx
: the most top-level app setup file.
❗️ This rule also applies to any top-level file you could have in your project.
Most of the time, this file sets up the portal for your app by referencing the most top-level component of your App, defined in another file, plus some additional top-level configuration.
As for every React app, you’ll need a router file to at least define the most top-level routes of your app: let’s keep it at the root directory for simplicity too.
How can I build my next React project fast using this scalable structure?
I created a template repository allowing you to start a React project with all the best tools I presented and some generators to help you develop faster, without losing scalability and robustness.
You are free to star this template repository ✨
This article is the second part of a larger trilogy, which goal is to give you all the best tools, advise you on how to use them and show you all the pitfalls you need to avoid when developing with React.
The first part can be read here. The third and last part will be released next week 🚀
Subscribe to not miss it!