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:


    1. create a constant for type action
    2. create action creator
    3. 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

    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:


    screenshot


    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.


    Also popular now: