(Re)act together
Component-based development for the web is fun. In many ways, it has changed the way we think about how we build for the web. But change isn’t always fun. Change brings about new challenges, which, can introduce new pains and frustrations into our workflows.
I’ve been involved in building several different React applications over the past few years. During the course of building them, one of the pains I noticed early on was the hassle of finding all the pieces the components in the project structure. Various component pieces were spread out in different places. It became clear that the organizational structures we had been using for apps built with different architectural patterns didn’t work as well for component-based applications. Definitely we need a better way.
What are the challenges when you build an application?
Here's the conversation you'll have with your fellow engineers:
- Do we use JSX?
Yes, absolutely.
- Well we shouldn't transpile JSX in the browser, so we'll use Babel to transpile it.
Can do - what version of Babel do you want to use?
- Version? Can we just use the latest?
Yeah, but version 6 made big breaking changes and invalidated a lot of existing docs.
- We can deal. What tool should we use to run babel?
Webpack, Gulp, Broccoli, vanilla NPM scripts, or Grunt
- Ok, which of those should we use?
Well, Gulp is easy to setup, but its losing popularity to Webpack. We can use Webpack to easily get hot-reload.
- Hot reload?
Yes, it reloads changed JS code in the browser
- Great, we'll use that.
Ok, we'll use React-Hot-Loader for now, but its being deprecated soon.
- Why is it being deprecated?
Its stable, but the author said its being deprecated in favor of React-Transform-HMR
- Whats the difference?
No clue.
- Ok, whatever, just pick one. Now I'm told that React is only responsible for the view layer.
Correct, it only handles views.
- So we need something to handle the state layer, what are our options?
Flux or Redux?
- Flux sounds good - lets go with that.
Great: do you want Facebook Flux, Fluxible, Reflux, Alt, Flummox, Marty, Mcfly, Lux, Material Flux, or Fluxxor?
- What? I want Flux?
Right, but there is no canonical implementation of Flux.
- Whatever, just go with Redux then.
Ok, but Redux just manages state.
- Huh?
It just holds state. We still need something for making requests, routing, binding Redux to React, binding the router to Redux, something to help with derived state...
- Bind Redux to React? What?
Yes, Redux isn't designed to work with React out of the box, you need the React-Redux libary.
- Ok, throw it on. Bind Redux to the Router?
Yes, Redux isn't designed to work with ReactRouter out of the box. You can use Redux-Router or Redux-Simple-Router.
- Ok, use the simple one. What about derived state?
You might want to transform your state before you use it. Use Reselect.
- 6 months later, after some other iterations, the code of the application gets really complicated to read and understand, everything looks like some Italian spaghetti pasta.
When I started to learn React, I found a few very good articles explaining how to create Todo lists or very simple games. Those articles were very useful to understand the basics of React, but I quickly got to a point where I wasn’t able to find much about how I could use React to build actual applications, something with a few dozens pages and hundreds of components.
After some research, I learned that every React boilerplate project on Github results to similar structures, they organize all the files by type. This might look familiar to you:
/src /actions /notifications.js /components /Header /Footer /Notifications /index.js /containers /Home /Login /Notifications /index.js /images /logo.png /reducers /login.js /notifications.js /styles /app.scss /header.scss /home.scss /footer.scss /notifications.scss /utils
index.js
This folder structure might be okay to build your website or application, but I believe that it is not the best folder structure.
When you organize your files by type, as your application grows, it often becomes difficult to maintain. By the time you realize this, it’s too late and you will have to invest a lot of time and money to change everything, or to support what you have for the next few years.
The good thing with React is that you can structure your application in any way you like. You are not forced to follow a certain folder structure, React is simply a javascript library.
What could be a better approach to organize your application?
For a couple of years I worked for a Companies which used Ember/Backbone as their main javascript framework to build all their new web applications. One interesting thing about Ember is the ability to structure your project by features, instead of by type. And this changes everything.
But I still wanted something much more flexible. After a few experiments, trying to find what would be the best structure, I got to a point where I decided to group all related features together, and nest them as needed. This is what I use now:
/src /components /Button /Notifications /components /ButtonDismiss /images /locales /specs /index.js /styles.scss /index.js /styles.scss /scenes /Home /components /ButtonLike /services /processData /index.js /styles.scss /Sign /components /FormField /scenes /Login /Register /locales /specs /index.js /styles.scss /services /api /geolocation /session /actions.js /index.js /reducer.js /users /actions.js /api.js /reducer.js index.js
store.js
Each component, scene or service (a feature) has everything it needs to work on its own, such as its own styles, images, translations, set of actions as well as unit or integration tests. You can see a feature like an independent piece of code you will use in your app (a bit like node modules).
To work properly, they should follow these rules:
- A component can define nested components or services. It cannot use or define scenes.
- A scene can define nested components, scenes or services.
- A service can define nested services. It cannot use or define components or scenes.
- Nested features can only use from its parent.
Note: By parent feature, I mean a parent, grandparent, great-grandparent etc… You cannot use a feature that is a “cousin”, this is not allowed. You will need to move it to a parent to use it.
Let’s break this down.
Components
You all already know what a component is, but one important thing in this organization is the ability to nest a component into another component.
Components defined at the root level of your project, in the components folder, are global and can be used anywhere in your application. But if you decide to define a new component inside another component (nesting), this new component can only be used its direct parent.
Why would you want to do that?
When you develop a large application, it happens quite often that you need to create a component that you definitively know you won’t reuse anywhere else, but you need it. If you add it at the root level of your components folder, it will get lost with hundreds of components. Sure, you could categorize them, but when it’s time to do some clean-up, you won’t remember what they are all for or if they are still being used somewhere.
Although, if you define at the root level only the main components of your application, such as buttons, form fields, thumbnails, but also more complicated one like listComments, formComposer with their own children components, it gets much easier to find what you need.
Example:
/src /components /Button /index.js /Notifications /components /ButtonDismiss /index.js /actions.js /index.js /reducer.js
- Button can be used anywhere in your application.
- Notifications can also be used anywhere. This component defines a component ButtonDismiss. You cannot use ButtonDismiss anywhere else than in the Notifications component.
- ButtonDismiss uses Button internally, this is authorized because Button is defined at the root level of components.
Scenes
A scene is a page of your application. You can see a scene just like any component, but I like to separate them into their own folder.
If you use React-Router or React Native Router, you can import all your scenes in your main index.js file and setup your routes.
With the same principle components can be nested, you can also nest a scene into a scene, and also define components or services into a scene. You have to remember that if you decide to define something into a scene, you can only use it within the scene folder itself.
Example:
/src /scenes /Home /components /ButtonShare /index.js /index.js /Sign /components /ButtonHelp /index.js /scenes /Login /components /Form /index.js /ButtonFacebookLogin /index.js /index.js /Register /index.js /index.js
- Home has a component ButtonShare, it can only be used by the Home scene.
- Sign has a component ButtonHelp. This component can be used by Login or Register scenes, or by any components defined in those scenes.
- Form component uses ButtonHelp internally, this is authorized because ButtonHelp is defined by a parent.
- The Register scene cannot use any of the components defined in Login, but it can use the ButtonHelp.
Services
Not everything can be a component, and you will need to create independent modules that can be used by your components or scenes.
You can see a service like a self-contained module where you will define the core business logic of your application. This can eventually be shared between several scenes or even applications, such as a web and native version of you app.
/src /services /api /services /handleError /index.js /index.js /geolocation /session /actions.js /index.js /reducer.js
I recommend you to create services to manage all api requests. You can see them as a bridge/an adapter between the server API and the view layer (scenes and components) of your application. It can take care of network calls your app will make, get and post content, and transform payloads as needed before being sent or saved in the store of your app (such as Redux). The scenes and components will only dispatch actions, read the store and update themselves based on the new changes.
Consultant - Technology driving digital innovation , Operational efficiency with Main Character Energy
6 年Excellent Article. This will be of Great help to us. Thank you for sharing an article on React.