Is it possible without Redux?

    Today you can find a lot of positions where react / redux is required. React is great, no questions asked. The question for Redux is whether it is possible without it. If you google a little, there is a solid article on Habré , where the author asks the same question. In an article with a simple example (todoList), the this.updateViews () method is called too often (seven to eight times) and it seems easier to do.

    The main idea here is observable models, react is responsible for observable, the only thing left is to create a model.

    Before creating the model, a few words about the design (architecture) of the client:

    index - raw data
    history - array [model]
    observer - model
    view - errors, focus, flags

    index.jsx - program entry point for the user screen. Index renders all components with default data, makes asynchronous queries, redraws components with new data.

    // index.jsx
    

    Observer.jsx is responsible for synchronizing the model for multiple views. For example, Petya fills out an offer form and sees real-time preview in the page header. Observer stores the model object, providing api: onModelChange (field, value) to the child components.

    History.jsx is a stack of model objects, where api: commit and rollback.

    Model.js is what the user can enter with pens - that is, the most valuable. Other data in the model does not need to be stored. Model is not a react component, but a regular js class.

    class Model {
      constructor(other = {}) {} // copy constructor (and default too)
      isEqual(other) {} // operator ==
      less(other) {} // operator< 
      swap(other) {}
      hash() {}
      fieldNameConstrains() {} //see below please 
    }
    

    The copy constructor is at least needed for History. The isEqual method is for popup-unsaved-changes (which is much more convenient than the flag in state). The fieldNameConstrains method is for dependent fields.

    Roughly speaking, if there are dependent fields, they need to be changed all in a row.

    class Model { 
       // constrains
       // distance  <== velocity * time
       velocityConstrains(newVelocity) {
         this.velocity = newVelocity;
         this.distance = this.velocity * this.time; 
       }
       timeConstrains(newTime) { … } 
       distanceConstrains(newDistance) {
         this.distance = newDistance;
         this.time = this.distance / this.velocity;   
       }
    }
    

    From personal experience, something like model.field.onchange does not work, since sometimes you need to call the copy constructor and onchange events are not needed at all.

    View.jsx

    class View extends React.Component {
      state = {
        errors: {},
        focus: {},
        …
      }
      render() {    
       …
         this.props.onModelChange(‘title’, e.target.value)} />
       …
      } 
    }
    

    Validation Validation does not need to be done in the model. It must be done in view (do not forget that view is the user's screen and that not the whole model can be shown on the screen). Validators are a set of predicates. There are only two algorithms for validation: 1) we find all errors in the form or 2) we find the first error. For instance,

    class View extends React.Component {
    onClickSaveButton() {
      const mapper =  {
        title: () => model.title.length && !maxLenValidator(model.title, 25),
        price: () => !(model.price % 40 == 0),
        url: () => !urlValidator(model.url),
        …
      }
      const errors = map(mapper, (validator, key) => {
        return validator() ? key : undefined;       
      }).filter(Boolean);
    }
    // валидаторы легко тестировать и легко переиспользовать
    

    Access rights. The main thing here is to stay and not use inheritance. The idea is that the model contains all the fields and we trim the fields for roles. That is, it is a whitelist, the remaining fields in the model remain by default. One step is added for validation - we make a projection of the validation object (it is also mapper, see above), that is, we validate only the necessary fields.

    About production. This approach has been spinning in production for a year now - it is an interface for creating advertising campaigns, including banners. Forms of varying complexity - ranging from one model to the screen, ending with many models of different types. Here you can add that the backend likes to send nested structures, you should not be shy and store only flat structures in the view.

    About the isEqual model method. Somewhere in utils.js there will be isEqual and isEqualArray methods:

      function isEqual(left, right) {
         return left.isEqual(right); 
      }
      isEqualArray(v1, v2) {
        if (v1.length !== v2.length) { return false }
        for (var k = 0; k != v1.length; k++) {
          if (!isEqual(v1[k], v2[k])) { return false; }
        }
        return true;   
      }   
    

    You need to try not to make models nested. Do not forget that the model is user data, not a data structure.

    Links:

    →  One
    Two

    Also popular now: