uncontrolled is an important concept in React. React encapsulates some form elements (input, textarea, etc.).

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

In fact, the idea of uncontrolled goes far beyond the form element, and proper use of uncontrolled component can greatly simplify code and improve project maintainability. This article will summarize the personal use of uncontrolled ideas in project practice with several common examples. Please point out any mistakes.

Maintainability advantages of Uncontrolled Component.

“High cohesion and low coupling” is an important principle in module design. For some pure UI components, the uncontrolled pattern encapsulates state within the component, reducing component communication, which is very consistent with this principle. The famous open source project React-Draggable provides a good example.

uncontrolled implementation of drag-and-drop components:

import React from 'react'
import Draggable from 'react-draggable'

class App extends React.Component {
  render() {
    return (
      <Draggable>
        <div>Hello world</div>
      </Draggable>); }}Copy the code

Controlled implementation of draggable components:

import React from 'react'
import {DraggableCore} from 'react-draggable'

class App extends React.Component {
   state = {
    position: {x: 0.y: 0}
  }

  handleChange = (ev, v) = > {
    const {x, y} = this.state.position
    const position = {
      x: x + v.deltaX,
      y: y + v.deltaY,
    }

    this.setState({position})
  }

  render() {

    const {x, y} = this.state.position
    return (
      <DraggableCore
        onDrag={this.handleChange}
        position={this.state.position}
      >
        <div style={{transform: `translate(${x}px, ${y}px) `}} >
          Hello world
        </div>
      </DraggableCore>); }}Copy the code

Comparing the two examples above, the uncontrolled component encapsulates the implementation logic of the drag, the state corresponding to the component location, and so on inside the component. As users, we don’t have to worry about how it works, and even if a BUG does occur, the scope of the problem can be locked inside the component, which is very helpful for improving the maintainability of the project.

A concrete implementation of the Mixed Component

The implementation of the react-Draggable function mentioned above is relatively complex. It is divided into two components based on controlled and uncontrolled. More often, one component carries two call modes. (Mixed Component) There are many examples such as Ant.Design:

  • PaginationIn the componentcurrentwithdefaultCurrent
  • SwitchIn the componentcheckedwithdefaultChecked
  • SliderIn the componentvaluewithdefaultValue

How can you better organize your code by combining the two patterns in one component? Take Switch as an example:

class Switch extends Component {
  constructor(props) {
    super(props);

    let checked = false;

    // 'checked' in props ? controlled : uncontrolled
    if ('checked' inprops) { checked = !! props.checked; }else{ checked = !! props.defaultChecked; }this.state = { checked };
  }

  componentWillReceiveProps(nextProps) {
    // If controlled mode, synchronize the props to simulate the effects of using this.props. Checked directly
    if ('checked' in nextProps) {
      this.setState({
        checked:!!!!! nextProps.checked, }); } } handleChange(checked) {// Controlled: only props. OnChange is triggered
    // uncontrolled: Internal change checked state
    if(! ('checked' in this.props)) {
      this.setState({checked})
    }

    this.props.onChange(checked)
  }

  render() {
    return (
      // Implement the UI according to this.state.checked)}}Copy the code

Uncontrolled extension of the idea in the classmodal component

In React projects, we usually call the Modal component in the following way:

class App extends React.Component {
  state = { visible: false }

  handleShowModal = (a)= > {
    this.setState({ visible: true })
  }

  handleHideModal = (a)= > {
    this.setState({ visible: false })
  }

  render() {
    return (
      <div>
        <button onClick={this.handleShowModal}>Open</button>
        <Modal
          visible={this.state.visible}
          onCancel={this.handleHideModal}
        >
          <p>Some contents...</p>
          <p>Some contents...</p>
        </Modal>
      </div>)}}Copy the code

According to the React rendering formula UI=F(state, props), there is nothing wrong with doing this. However, if a large number of Modal-like components are used in a component, we have to define a large number of Visible State and Click Handle functions to control the expansion and closure of each Modal, respectively. The most representative is the custom Alert and Confirm component, if every interaction with the user must be controlled by state, it is too cumbersome, inexplicably increase the complexity of the project. Therefore, we can take the idea of uncontrolled and try to encapsulate the closure of the component within the component, simplifying a lot of redundant code. Take the Alert component as an example:

// Alert UI component that binds destroy to the place you want to trigger
class Alert extends React.Component {
  static propTypes = {
    btnText: PropTypes.string,
    destroy: PropTypes.func.isRequired,
  }

   static defaultProps = {
    btnText: 'sure',
  }

  render() {
    return (
      <div className="modal-mask">
        <div className="modal-alert">
          {this.props.content}
          <button
            className="modal-alert-btn"
            onClick={this.props.destroy}
          >
            {this.props.btnText}
          </button>
        </div>
      </div>)}}// An intermediate function for rendering that creates a destroy and passes it to the Alert component
function uncontrolledProtal (config) {
  const $div = document.createElement('div')
  document.body.appendChild($div)

  function destroy() {
    const unmountResult = ReactDOM.unmountComponentAtNode($div)
    if (unmountResult && $div.parentNode) {
      $div.parentNode.removeChild($div)
    }
  }

  ReactDOM.render(<Alert destroy={destroy} {. config} / >, $div) return {destroy, config}} /** * Because of the elegance of API syntax, we often export components with similar functionality. Such as:  * https://ant.design/components/modal/ * Modal.alert * Modal.confirm * * https://ant.design/components/message/ * message.success * message.error * message.info */ export default class Modal extends React.Component { // ... } Modal.alert = function (config) { return uncontrolledProtal(config) }Copy the code

Now that we’ve completed an Alert in uncontrolled mode, it’s easy to call without having to define state to control show/hide. The online preview

import Modal from 'Modal'

class App extends React.Component {
  handleShowModal = (a)= > {
    Modal.alert({
      content: <p>Some contents...</p>
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.handleShowModal}>Open</button>
      </div>)}}Copy the code

conclusion

uncontrolled Component has some advantages in terms of code simplicity and maintainability, but it should also be used in a scenario where “you really don’t care about the state inside the component.” In sufficiently complex projects, most scenarios still require complete control over all component states (e.g., undo). Learning something is not always available everywhere, it is important to remember it subconsciously in the most suitable scene.

  • reference

  • Github.com/reactjs/rea…

  • Github.com/mzabriskie/…

  • Github.com/ant-design/…

  • Github.com/react-compo…

  • The source code involved: github.com/HIceFire/de…