A
A
Artyom2018-09-17 13:15:44
Software design
Artyom, 2018-09-17 13:15:44

What architecture of reactjs/redux application do you have?

Good afternoon!
I am writing a client-side application using reactjs and redux.
After several months of work on the project, questions arose about the architecture of the project, the organization of the redux.
There is the following structure:

├── src
│   ├── assets
│   ├── components
│   │   ├── App.jsx
│   │   ├── blocks
│   │   ├── common
│   │   ├── settings
│   │   │   ├── components
│   │   │   │   ├── accounts
│   │   │   │   ├── buttonDotted
│   │   │   │   ├── collections
│   │   │   │   ├── settingsFilter
│   │   │   │   └── stores
│   │   │   │       ├── components
│   │   │   │       │   └── store
│   │   │   │       ├── stores.css
│   │   │   │       └── stores.jsx
│   │   │   ├── settings.css
│   │   │   ├── settings.jsx
│   │   │   ├── settingsNavigation.jsx
│   │   │   └── settingsRouter.jsx
│   │   └── signup
│   ├── constants
│   ├── index.js
│   ├── store
│   │   ├── actions
│   │   ├── constants
│   │   ├── reducers
│   │   └── store.js
│   └── util

I’ll make a reservation right away that the project structure is naturally not all and the names of the components have been changed for security reasons, any matches are random))
Let’s continue on the topic:
Inside the src folder we have a skeleton:
assets - any statics (fonts, pictures, general css) ...
components - react code , components both "smart and dumb";
components/blocks - smart components that are connected to the redux store and used in more than one place in the application;
components/common - stupid components (button, input...);
components/settings, components/signup - application pages;
constants - constants for the application;
store- all good for redax;
util - self-written utilities;
index.js is the entry point.
More about src/components .
Pages ( signup for example - components/signup/signup.jsx ) are connected to the store.
If you need to separate the page view from its logic, signupComponent.jsx will be next to signup.jsx , as it is done:
----signup
├── components
├── signup.css
├── signup.jsx
└── signupComponent.jsx

In the case above, signup.jsx will be connected to the redux store and will have some kind of logic (form validation, page local state, handlers for various events such as calling the password recovery modal, etc.) and forward everything you need to signupComponent.jsx , and signupComponent. jsx is a stupid component that collects styles, components from the signup / components folder into one view (page), passes props further into the necessary components such as forms, inputs, etc. The components
folder inside the page?
Yes - in each page folder, if necessary, there is a components folder that will contain components specific to this page (both smart and stupid).
If you look at the folder structure, then the settings page has settingsRouter.jsx , we have nested routing inside settings. Also inside settings/components there are components accounts, collections, stores - child routes for settings, which in turn are connected to the editor.
The settings.jsx itself is not connected to the redux store - in my case it's just a wrapper for arranging navigation and views of child routes. Using the components/settings/components/store
example, you can see that there is also a components folder with components for stores , thank God the components folder is the last one in this subtree.
Such nesting of the components/ folders in each page is available for all pages and, as necessary, in the blocks folder - "smart components".
The workflow with this goodness is this: of a fractal structure .
If a smart component needs to be reused on another page, then it is sent from the components folder of the page in which it was used to the src/blocks folder .
If there is a view that needs to be reused on another page, then it is sent to the src/common
folder. On the one hand, it seems clear right away what goes where and to whom. And it reminds us of something Our project is small, about 10 pages. Given the specifics of the project, adding new features to such a structure will not hurt. But something worries, the eye is not entirely pleasant. All the time it seems that the folder/project organization is not "perfect". Here we come to the first questions, even to the request to express recommendations by looking at the project with your "fresh eye".
1) How much do you think my approach differs from the same fractal structure of the project for the worse/better?
2) Subjectively, is it more convenient to place nested components in the components folder or at the same level of the page folder as suggested in the article on fractal structure?
3) What can my structure threaten in the future?
4) If you also used atomic architectureor have an opinion on this matter, then share the pros / cons in comparing the approach I have chosen and the atomic architecture.

It is very interesting to hear the opinions and study the recommendations of the experienced.
Next on the agenda is the editor's favorite. There is a src/store
directoryin which all actions, reducers, constants for redux (action types) are included, and store.js itself , which collects reducers and gives the created store the entry point:
├── store
│   ├── actions
│   │   ├── signup
│   │   └── store
│   │       └── store.js
│   ├── constants
│   ├── reducers
│   │   ├── index.js
│   │   ├── signup
│   │   │   └── index.js
│   │   └── store
│   │       ├── connectToStore.js
│   │       ├── getStores.js
│   │       ├── store.js
│   │       └── syncStore.js
│   └── store.js

First did on off. redux docs - src/actions , src/reducers , action constants in src/constants . As the application grew, it became inconvenient to jump between folders. Consolidated everything related to the redax in the src / store folder up to a separate constants folder. It has become much more convenient. Each type of action in store/actions and store/reducers has its own folder - also convenient, okay.
BUT! There is one fat but - wild code duplication.
As for actions, I want something beautiful, it is clear what is happening: action code .
The function that we forward to the desired component to request a list of some useful information -getStoresAction . Starting to use this approach, the advantages are immediately obvious - everything is under control and you can see what is responsible for what. We know exactly when to spin the download spinner, when to throw an error message.
To understand what stage we are at, we need 3 actions for each stage, these are:
getStores, getStoresSuccess, getStoresFailure .
What's in the reducer: reducer code
Also convenient, everything is simple and clear.
The reducer directory contains files with imports of functions that process different actions in the reducer. What they look like:
syncStore code and getStore code
The code began to grow, copy-paste too. I reassured myself with the thought that this approach gives full control and in which case, I can easily make changes to the work of one or another part of the application without affecting its other parts. But as the application grew, I realized that I spend a lot of time on copy-paste and re-creation of the same actions and reducers (with different names :) ) - which is a pity.
I started to google and all the options that I found subjectively were quite complicated and assumed in my case to rewrite about half of the project - alas, not an option.
I met an approach in which all smart components included their actions, reducers, constants and everything that he needed, conditionally it could be ported to another project - a modular architecture like. What is interesting is that the main reducer there somehow cleverly collects reducers from all components and assembles them into one reducer by substituting the names of actions from the component, something like: and so on. At the same time, using the same handler for actions with the ".FETCH" prefix, and another one for ".FETCH_SUCCESS". And there was no duplication of code like mine. In general, I'm leaning towards such an architecture in the next project. But I did not delve into it, because the current project does not need such a deep refactoring.`${store}.FETCH`, `${store}.FETCH_SUCCESS`
I also had thoughts to make several common functions for reducers and process the same places with the same functions, which will reduce the total amount of code in reducers, but slightly reduce flexibility.

5) What do you think of the modular structure? Do you have any examples you can share?
6) How do you deal with code duplication in reducers and actions like mine?
7) Based on the code that I have now, is it possible to get off with little blood and get rid of code duplication?
8) How do you organize your redux?
But I need to change the received data. An array of objects came from the server, and I need to add a couple of fields with boolean values ​​to each object (case: on one page we can select stores, and based on the selection, data will be loaded, and on the other page we can also select the same stores, but on based on the selected stores, let's say analytics will be loaded). So we need to add two fields to each store object: checkedInStoresPage, checkedInAnalyticPage. 9) The question is, where to collect the logic that makes transformations with the data, in an action or in a reducer? And the last:
I have actions that make requests to api.

There are actions that don't make API calls, like the one in the question above for changing the checkedInStoresPage/checkedInAnalyticPage state.
Now I have the following action:
toggleStoreAction code
It works, it's simple and easy.
10) But is it possible to store such logic in actions or put it in a reducer?
Thank you for your time, have a nice day!

Answer the question

In order to leave comments, you need to log in

3 answer(s)
A
Anton Spirin, 2018-09-18
@SaymonA

There are two good approaches to codebase organization that are suitable for most projects: File Type First and Feature First:
Проект:

/common
  /api
  /components
  /ducks
  /entities
  /sagas
  /selectors
  /utils
/features
  /Feature1
  /Feature2
  /Feature3
  /Feature4
  ...
  /FeatureN
/Main
  /pages
  index.js
  App.js
  routes.js
  rootReducer.js
  rootSaga.js
  store.js
/Auth
  /pages
  index.js
  App.js
  routes.js
  rootReducer.js
  rootSaga.js
  store.js
...

Отдельно взятая Feature:
/features
  /Accounts
    /components
    index.js
    accountsDucks.js
    accountsSaga.js
    accountsSelectors.js
    accountsApi.js
    Accounts.js
    AccountsContainer.js

 
Example File Type First
/actions
/common
/components
  /core
  /Feed
  /Profile
  ...
/constraints
/containers
/entries
/locales
/pages
/reducers
/utils
...

Both approaches, when used skillfully, are highly scalable, maintainable, and cause no refactoring issues.
Depending on tasks, date mappers can be used in reducers, in mapStateToProps and asyncActions. The main thing is that the project should be standardized.
In mapStateToProps , they write the transformations necessary for only one component.
A lot of boilerplate is the price you pay for using redux. You can write all the constants and reducers by hand, you can use libraries like redux-actions and the like. In the first case, you get a plus in flexibility, readability and static analysis, in the second, less code. In most projects, I prefer the first option. I also create file templates in Webstorm for asyncActions, contstraints, reducer, page, component and final component.
In specific projects with many CRUD requests and similar entities, it makes sense to write CRUD Boilerpalte.

N
nakree, 2018-09-18
@nakree

src/
--utils/
--config/
--css/
--img/
--components/
----User/
------User.jsx
------UserContainer.js
---- --UserReducer.js
------UserActions.js

A
Alexey Nikolaev, 2018-09-17
@Heian

Refused to divide the components into smart and stupid. Any component may need direct access to data, and throwing noodles into props is a thankless task. In general, everything is simple for me: components in the Components folder, reducers in the Reducers folder, actions in Actions, constants in Constants, and everything else as intended.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question