We wanted to create an application that is easy to extend and also easy to maintain. In order to achieve that, there has to be clear separation of concerns made by design. We believe that project folder structure is the part where you can enforce many good practices from ground-up. Essenially decreasing the burden on developers.
We have couple of building blocks and 'binders' in react-redux application. We create components
that represent the view, we create containers
that connect views to state and we create actions/sagas/reducers
trio to maintain the state.
We will also need pages/routes that will display all the containers on one page to user.
We can easly say that the biggest building block of web applications are pages (routes). They represent the view user will get entering our page, contain information what he will and not see on given address. We decided that this will be good point of splitting the responsibilites.
As we we decided to split concerns on the level of page we create a container
for each page in our application.
In our terminology container
will mean component that is connected to state and has actions/sagas/reducers
the trio (if neccessary).
Example If page has it's own state - let's say - edit form will have validation results that only this page is concerned with. The reducer and appropriate actions for this page would be created and would land in container of this page, they would exist right beside component
representing the View and the redux-container
that connects the page to state.
If there was a need for having a more granular approach we suggest you to create a smaller container, that would also contain all it's concers - the view in a seperate file, the connector and the the trio.
If you got to the point where single component you want to display changes behaviour completly based on place you use it on, you should create a pure component inside Components/ folder that any container can access.
To access state that is on global level we suggest modules/
approach. In there the trio (sagas/reducer/actions
) resides, providing access to domain level state, as for example list of users, list of processes etc. any state, that isn't connected to any page/container but needs to be shared across application.
You can communicate with different containers via imports of their actions that should look something like:
import {changeStatsFilter} from 'containers/ProcessesStatistics/actions'
Based on this we created an example structure like this:
.
│ client.jsx
│ routes.js
│ store.js
│
├───components
│ └───StyledProcessesSearch
│ index.jsx
│ style.scss
│
├───containers
│ ├───App //Application level container - Main layout etc.
│ │ actions.js
│ │ constants.js
│ │ index.js
│ │ reducer.js
│ │ sagas.js
│ │ selectors.js
│ │
│ ├───ProcessDetailsPage //Page without specific state for it's own, relies on already defined selectors (modules/)
│ │ constants.js
│ │ index.jsx
│ │ ProcessDetailsAppBar.jsx
│ │ ProcessDetailsContainer.jsx
│ │
│ ├───ProcessEditPage //Rich page - has own actions, and own sagas, that impact its state.
│ │ │ index.jsx
│ │ │ ProcessEditAppBar.jsx
│ │ │ ProcessEditContainer.jsx //Container that may use some generic components, but can also defined it's own structure
│ │ │ reducer.js
│ │ │ selectors.js
│ │ │
│ │ ├───actions
│ │ │ clearEditProcessForm.js
│ │ │ editProcess.js
│ │ │ editProcessesInputValidate.js
│ │ │ index.js
│ │ │
│ │ └───sagas //sagas specific for ProcessEditPage
│ │ handleEditProcess.js
│ │ handleValidationResult.js
│ │ index.js
│ │
│ ├───ProcessesStatistics //Container without specific state, relies on global state
│ │ constants.js
│ │ index.jsx
│ │ ProcessesStatistics.jsx
│ │ ProcessesStatisticsViewModel.d.ts //Typescript definition that is valid for this container (could be typings.d.ts)
│ │ selectors.js //selector that takes neccessary data from global state and maps to container
│ │
│ └───ProcessesTable
│ actions.js
│ constants.js
│ index.jsx
│ ProcessesTableComponent.jsx
│ ProcessesTableContainer.jsx
│ reducer.js
│ sagas.js
│ selectors.js
│
├───i18n
└───modules //State and actions that is shared across components most often 'domain' related
└───processes
actions.js
constants.js
models.d.ts
reducer.js
sagas.js
selectors.js
services.js