Real world React learnings, part two
Developer productivity can be affected by the most various factors. Recently, we’ve discovered project file structure can play a large role and that it deserves some thought.
This is the second post of a three parts series. The first post can be found here.
Project file structure: We started with the Rails-style folder structure, i.e., separated folders for
sagas, but found some issues with this.
Project file structure is more of a matter of personal taste for you and your team than a rule written in stone. In this article, I’d like to show how we initially structured our app, why it didn’t work for us and what we changed into. The caveat I make is that the structure that worked for us may not work for you!
Initial file structure: Rails-style (or file type based)
Most tutorials and examples (such as the ones from the Redux repository) use this structure which is probably the most common way to organize a React+Redux project. A project structured this way looks like this:
│ └── HomeActions.js
│ └── HomeContainer.jsx
│ └── HomeConstants.js
│ └── HomeReducer.js
Some of its advantages are:
- Easier to start: This is pretty intuitive and there are lots of examples out there that use this structure.
- Grouped imports: There are some types (like
sagas) that must be imported together, and having them all in one folder makes the job easier.
Why it didn’t work for us
Even though grouping the same type of files in the same folder is pretty intuitive, knowing which group of
action related to what feature isn’t so much. Another pain is navigating through the project, because usually you’d need to have a lot of top level folders open on the sidebar when editing a feature, and each folder can have a lot of files often unrelated to your task at hand, the pain gets bigger if the person is a newcomer to the project.
We also noticed that most of our files had a one-to-one relationship with the features developed, it wasn’t unusual to have an
saga file with the same name on each folder, e.g.,
Another thing that happened was that we’d get really annoyed by all the extra Redux boilerplate and would sometimes choose not to use Redux on some features, which I think it’s bad (for me, one of the advantages of using Redux is enforcing a standard) but understandable (the famous deadline pressure) as long as it doesn’t break standards too much, like having
components, which are supposed to be presentational, having business logic or fetching to some API. I know that this is more of a Redux issue than a file structure one, however, having the project structured in a way that reflects Redux file types lowers our flexibility if we wanted to use a different state management library.
I’ve seen some projects that are adopting this structure already such as marmelab’s amazing react-admin and Rekit. Projects organized this way have folders named after the features and not the types (
actions, reducers, etc). This approach results in a project like this:
│ ├── actions
│ │ └── HomeActions.js
│ ├── containers
│ │ └── HomeContainer.jsx
│ ├── constants
│ │ └── HomeConstants.js
│ ├── components
│ │ └──...
│ └── reducers
│ └── HomeReducer.js
This way, when developing a new feature, you just have to open a new folder and see all the files actually needed for the feature, making debugging and navigating much easier.
Since one of the main advantages of React is its component philosophy, we decided to leave all reusable inside a
components/ folder on the root of our project. Also, we decided to have a
shared/ with reusable logic (authentication, general feedback, form validation, etc).
- Encapsulation: Almost all files related to a feature are in the same folder (maybe there are some dependencies between features) and as long as you have a pattern for importing/exporting, there are no big surprises when using these modules. Also, you can have other state management solutions inside a feature (sometimes redux boilerplate is overkill), for example, React “vanilla” state on top-level container was used on some screens.
- Code splitting: This becomes quite trivial when you structure the folders this way.
The only problem we had is that
redux still needs a root reducer and since we use
redux-saga, a root saga as well, thus, we had to always remember to import those on
We still need to decide on a standard for
containers with routes, because I think it’s really important to explicit components that have routing logic.
Also, most of the boilerplate that troubled us so much could be automated and making a Yeoman generator for new
components and new
features would be a good idea.
All things considered, I’d say the most important thing is having consistency on your standards. Maybe just having more meaningful files names would’ve solved our problem, eliminating the need to change our structure, however, it made so much sense for us that we’re still happy with this choice.