
Redux-Redents - (yet) one module for working with server data from React-Redux applications.
React and Redux, recently one of the most popular buzz-words in the frontend world. Therefore, when I needed to make a web application that would display the data received from the server, as well as allow it to be manipulated (create, delete, and modify), I decided to build it on the basis of a bunch of React and Redux. Many of the getting-started tutorials cover only the component creation functionality, action creators and reducers. But as soon as it comes to exchanging with the server, difficulties begin - the number of necessary action creator, reducers is growing. Moreover, they are very similar to each other, with mini-differences. In most cases, only in the type (name) of activity. After I created the third identical set of creators and reducers, there was a desire to change something. So the idea of implementation was bornredux-redents .
Start, dictionary reducers
In general, reducers are very similar to each other - take your action and create a new state for the repository based on it. If we consider reducers for processing the response from the server, then they will differ only in the type of action. So the idea of a “universal” reducer was born:
function createReducer(acttype,initialState) {
return (state = initialState, action) => {
if(action.type!=acttype) return state;
return action.res.data;
};
}
const dicts = {
type1 : createReducer(TYPE1_CONSTANT,{}),
type2 : createReducer(TYPE2_CONSTANT,[])
}
const rootReducer = combineReducers({...dicts});
This already allows not writing the same functions and not fussing switch-case constructions.
Constants Deliverance.
The reducers dictionary reduced the amount of the same code, but there were constants for specifying action types. Adding a new action and its handler looks like this:
- create a constant for type action
- create action creator
- create a createReducer handler with the given type.
Support for a set of constants begins to annoy almost immediately. Moreover, there is practically no sense in them - the developer uses them only for a bunch of creator and reducer. In this way, constants can be replaced with conventions for configuring action types.
Next - all action creators for receiving data from the server look the same - create an action with the desired type and promise for the request to the server. If they look the same, isn’t it better to automate the process of creating creators, and even better to make a universal creator?
The combination of two ideas - the substitution of constants by conventions and the universal creator and led to the birth of the module.
Data agreement
If rest-like api is used to communicate with the server, then for each data type we have the same number of default operations: index (list), get, post, delete; and each operation has uri and parameters for transmission to the server. Thus, you can enter into default agreements:
- each data type supports a standard set of operations
- rules for calculating url and parameters are defined for each operation
In addition, it is necessary to provide for the possibility of expansion:
- adding operations
- query design
As a result, the following format appeared:
entities = {
fruit : {}, //all default
vegetable: {
crop : { //custom operation
type: 'CROP_VEGETABLE',
request: (data) => return post(url,data) //custom request
},
index: {
url: url1 //custom url
}
}
}
Universal action creator
Now, to simplify life, it remains to implement universal actions creator. And again, agreements come to our aid:
- standard set of operations - index, get, post, delete
- url calculation rules -
url = base+entity+'s'
- rules for passing parameters
- get, delete -
url = url+'/'+data
- post - send data in the request body
- get, delete -
The universal action creator allows you to do the following:
this.props.entityOperation('fruit','index'); //загружает список фруктов
this.props.entityOperation('fruit','get','apple'); //получает фрукт по имени 'apple'
this.props.entityOperation('fruit','post',{name:'orange',id:'5'}); //обновляет или создает 'orange'
this.props.entityOperation('vegetable','crop',{name:'carrot'}); //выполняет операцию crop над parrot
Creator creates an action with promise to send / receive data to the server. Promise needs to be handled somehow and redux middleware comes to the rescue here
Middlewares
Redux middleware is a function that takes action and the next handler in the chain and that can process the action itself and / or pass it further down the chain of handlers. To process the promise, we need to take the original action, install promise handlers and modify the action to show that the system is in a state of requesting data to the server. For modification, you can either add fields to action, or modify its type. I chose a type modification.
Promise middleware
- changes type
action.type = action.type+'_REQUEST';
- creates a success handler in promise
переслать в следующий обработчик исходный action с ответом сервера
- creates an error handler
модифицировать тип - action.type=action.type+'_ERROR' и переслать в следующий обработчки ошибку, полученную с сервера
- returns promise
Promises have earned, the data comes from the server, but the ability to call action after the completion of another has become lacking. For example, update data from the server after saving data to it. This is how Chain Middleware was invented - a function that executes an action creator after processing the previous action.
Chain middleware
To implement call chains, the last parameter to the universal action creator was a function that generates a new action, which receives the server response (if it exists) or the original action (otherwise).
The generating function is called only if the action being processed contains the status field with the value 'done' (action.status == 'done')
this.entityOperation('fruit','save',fruit,()=>this.props.entityOperation('fruit','index'));
Module
A natural desire was to share these ideas and their implementation - the redux-redents module was born. The module is available for installation via npm
npm install --save redux-redents
Usage example
As an example, a client-demo application has been developed
git clone https://github.com/kneradovsky/redents/
cd client-demo
npm install
npm start
the last team will assemble the application, launch the development server and open the start URL of the application in the browser. The
application contains one page that displays a list of fruits and allows you to add, delete and edit fruits. The page view in the screenshot below:
Conclusion
I will be glad if my module is useful. Open for questions, comments and suggestions for expanding functionality. The source code of the module, as always, is available in the GitHub repository.