Five Component Forms in React

Keywords: React Programming axios

At present, the mainstream technology of front-end development has developed towards componentization. When learning a new framework, the most basic part must be to learn how to write its components. It's like learning a new programming language, always starting from hello world. And in React, what are the common ways to write components? Or how many different kinds of components can be classified?

Stateless components

Stateless Component is the most basic component form, because there is no effect of state, it is the role of pure static display. Generally speaking, various UI libraries are also component categories that will be developed initially. Such as buttons, labels, input boxes, etc. Its basic structure is the property (props) plus a render function (render). This component is also the most reusable because it does not involve state updates.

const PureComponent = (props) => (
    <div>
        //use props
    </div>
)

Stateless components are written in a very simple way. I usually declare this component form directly using the arrow function provided in ES6 grammar, rather than using the traditional component definition. Of course, if you encounter slightly more complex ones, you may also have hook functions with a lifecycle. Class Component is needed at this time.

Stateful components

On the basis of stateless components, a stateful component is formed if the state is contained within the component and changes with events or external messages. Stateful components usually have life cycles that trigger state updates at different times. This component is also commonly used in writing business logic, depending on the number of States and lifecycle mechanisms of components in different business scenarios.

class StatefulComponent extends Component {

    constructor(props) {
        super(props);
        this.state = {
            //Define state
        }
    }

    componentWillMount() {
        //do something
    }

    componentDidMount() {
        //do something
    }
    ... //Other life cycles

    render() {
        return (
            //render
        );
    }
}

Container assembly

In the specific project practice, we usually get the front-end data through Ajax requests, and the back-end data also need to be further processed. In order to simplify the responsibilities of components, the concept of container components is introduced. We put the logic of data acquisition and processing in container components, which further reduces the coupling of components.

var UserListContainer = React.createClass({
  getInitialState: function() {
    return {
      users: []
    }
  },

  componentDidMount: function() {
    var _this = this;
    axios.get('/path/to/user-api').then(function(response) {
      _this.setState({users: response.data});
    });
  },

  render: function() {
    return (<UserList users={this.state.users} />);
  }
});

For example, the container component above is responsible for obtaining user data and then passing it to the UserList component in the form of props for rendering.

High-order components

In fact, for general small and medium-sized projects, you only need to use the above three components to construct the required applications. But when faced with complex requirements, we can often use high-level components to write reusable components. So what are higher-order components? In fact, it is similar to the concept of higher-order functions, that is, a component that returns components. Or rather, it's actually a function that returns components. Like this:

const HigherOrderComponent = (WrappedComponent) => {
  return class WrapperComponent extends Component {
    render() {
      //do something with WrappedComponent
    }
  }
}

As a higher-order function, new functions and behaviors can be added to the original components. We usually want to write components as pure as possible or that the business logic is relatively simple. But if new functions such as printing logs, acquiring data and verifying data, and displaying irrelevant logic are needed among various components, these common codes will be repeated many times. Therefore, we can abstract a high-order component to add these functions to the underlying component, similar to the effect of plug-ins.

A more common example is form validation.

//Verification Rules, Form Components
const FormValidator = (WrappedComponent, validator, trigger) => {

   getTrigger(trigger, validator) {
      var originTrigger = this.props[trigger];

      return function(event) {
          //Trigger the validation mechanism to update the status
          // do something ...
          originTrigger(event);
      }
  }

  var newProps = {
    ...this.props,
    [trigger]:   this.getTrigger(trigger, validator) //Trigger timing, rebind the original trigger mechanism
  };

  return <WrappedComponent  {...newProps} />
}

It's worth mentioning that it's also a way to add new functionality to components, which is simpler and simpler than using mixins for higher-level components. If you use multiple mixins, state contamination is very easy to occur, and it is difficult to see the logic implicit in mixins from the definition of components. The higher-order components are easier to maintain.

On the other hand, the new ES7 grammar Decorator can also be used to achieve the same effect as the above.

function LogDecorator(msg) {
  return (WrappedComponent) => {
    return class LogHoc extends Component {
      render() {
        // do something with this component
        console.log(msg);
        <WrappedComponent {...this.props} />
      }
    }
  }
}

@LogDecorator('hello world')
class HelloComponent extends Component {

  render() {
    //...
  }
}

Render Callback component

Another component pattern is to delegate the rendering logic in a component to its subcomponents by using the rendering callback method in the component. Like this:

import { Component } from "react";

class RenderCallbackCmp extends Component {
  constructor(props) {
    super(props);
    this.state = {
      msg: "hello"
    };
  }

  render() {
    return this.props.children(this.state.msg);
  }
}

const ParentComponent = () =>
  (<RenderCallbackCmp>
    {msg =>
      //use the msg
      <div>
        {msg}
      </div>}
  </RenderCallbackCmp>);

The parent component captures the internal rendering logic, so it can be used when you need to control the rendering mechanism.

summary

These compiling modes can basically cover the patterns needed in the current work, especially when writing the same framework components, careful design and study of decoupling and composition between components can greatly enhance the maintainability of subsequent projects.

Reference Documents

React Patterns - Render Callback
React Higher Order Components in depth

Posted by deregular on Wed, 12 Jun 2019 12:46:06 -0700