skip to Main Content

I am going through the react documentation article of integrating-with-jquery-chosen-plugin, and react gives an example of the Chosen jquery plugin and mentions the following:

Notice how we wrapped <select> in an extra <div>. This is necessary because Chosen will append another DOM element right after the <select> node we passed to it. However, as far as React is concerned, <div> always only has a single child. This is how we ensure that React updates won’t conflict with the extra DOM node appended by Chosen. It is important that if you modify the DOM outside of React flow, you must ensure React doesn’t have a reason to touch those DOM nodes.

class Chosen extends React.Component {
  render() {
    return (
      <div>        
        <select className="Chosen-select" ref={el => this.el = el}>          
          {this.props.children}
        </select>
      </div>
    );
  }
}

I initially thought that when react re renders <App> with new setState() call, if we don’t have the extra div around the select then there would be trouble for the diffing algorithm and dom elements appended after select would be removed. But, apparently this is not whats happening. In terms of code:

<App>                   |                 |<App> 
  <Component1/>         |                 |  <Component1/>   
  <Chosen/>             | -- rerender --> |  <Chosen/>  //gets updated and removes nodes after <select>
  <Component2/>         |                 |  <Component2/> 
</App>                  |                 |</App>    

I tried to imitate this situation and to my surprise, even if I don’t wrap select in a div, it doesn’t get updated and the extra dom nodes added after select stay intact:

//class component Chosen
class Chosen extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      chosen: this.props.chosen,
    };
    //I haven't used this state, but even if the option tag has value as this state
    //On state update, the appended red input doesn't get deleted
  }



  render() {
    return (
      <select id="unique">
          <option value="one">one</option>
          <option value="two">two</option>
        </select>
    );
  }
}



class App extends React.Component {
  //create state for fancybtn1 and 2
  constructor(props) {
    super(props);
    this.state = {
      fancyBtn1: "one",
      fancyBtn2: "two",
    };

    //after 5 seconds update state
    setTimeout(() => {
      this.setState({
        fancyBtn1: "three",
        fancyBtn2: "four",
      });
    }, 5000);

  }

  //componentDidMount
  componentDidMount() {
    console.log("componentDidMount");
    //grab #unique and append an input tag after it
    setTimeout(() => { //jquery plugin like dom manipulation //$("#unique").Chosen();
      const unique = document.getElementById("unique");
      const input = document.createElement("input");
      input.type = "text";
      input.style.border = "1px solid red";
      input.value = "test";
      unique.after(input);
    }, 2500);
  }




  render() {
    console.log("App render");
    return (
      <div>
        <h1>App {this.state.fancyBtn1} </h1>
        <FancyBtn txt={this.state.fancyBtn1} />
        <Chosen chosen={this.state.fancyBtn1} />
        <FancyBtn txt={this.state.fancyBtn1} />
      </div>
    );
  }
}

let FancyBtnCount = 0;

//create FancyBtn class based component which has state for txt
class FancyBtn extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      txt: this.props.txt,
    };


    //set counter, which keeps track of how many times the component is instantiated
    //I couldn't do it properly atm.
    FancyBtnCount++;
  }

  //on component update
  componentWillReceiveProps(nextProps) { // I know gives deprecated warning 
    console.log("componentWillReceiveProps");
    //set state if prev state is different
    if (this.state.txt !== nextProps.txt) {
      this.setState({
        txt: nextProps.txt,
      });
    }
  }



  render() {
    return (
      <div>
        <h1>FancyBtn {FancyBtnCount} </h1>
        <input type="text" value={this.state.txt} />
        <button>{this.state.txt}</button>
      </div>
    );
  }
}







ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id="app"></div>

After 2.5 seconds I add a red input dom node after select and after 5 seconds, I re render the whole app. Now why doesn’t with the re render the react diffing algorithm remove the red input? What difference would it make if I wrap the select in an extra div? Basically I am looking for an explanation of the quoted react docs.

2

Answers


  1. This is most likely not a complete answer, but too long to post it as a comment.

    In this specific case you are defining a react component named Chosen:

    class Chosen extends React.Component {
      render() {
        return (
          <div>        
            <select className="Chosen-select" ref={el => this.el = el}>          
              {this.props.children}
            </select>
          </div>
        );
      }
    }
    

    In React, a component can never return multiple "top level" elements, because it wouldn’t make any sense.

    So an even simpler component like this:

    class Chosen extends React.Component {
      render() {
        return (
          <div>        
          </div>
          <div>
          </div>
        );
      }
    }
    

    is "illegal", because a component has to always return only one "top level" element. If for some reason you want your component to return multiple elements like in the example above, you’d have to wrap them into a div:

    class Chosen extends React.Component {
        render() {
            return (
                <div>
                    <div>
                    </div>
                    <div>
                    </div>
                <div>
            );
        }
    }
    

    and now you would be correctly returning only one top-level element, which then in turn has those two divs as a children.

    So in your case if you were to do it like this:

    class Chosen extends React.Component {
      render() {
        return (     
            <select className="Chosen-select" ref={el => this.el = el}>          
              {this.props.children}
            </select>
        );
      }
    }
    

    it wouldn’t work (even though it looks like there is only one element) because the chosen library apparently works like so that it will turn that select component into something like this for example:

    class Chosen extends React.Component {
      render() {
        return (  
            <div class = "chosen-select-label">My select</div>   
            <div class = "chosen-select">
              <select>
                <option></option>
                <option></option>
              </select>
            </div>
            <div class = "some-other-chosen-select-shenanigans">
              ...
            </div>
        );
      }
    }
    

    Now React has a problem with this because the component is returning more than one element. However, if you wrap the select into a div, the result would be something like this:

    class Chosen extends React.Component {
      render() {
        return (
          <div>
            <div class = "chosen-select-label">My select</div>   
            <div class = "chosen-select">
              <select>
                <option></option>
                <option></option>
              </select>
            </div>
            <div class = "some-other-chosen-select-shenanigans">
              ...
            </div>
          </div>
        );
      }
    }
    

    and React will be happy again.

    As far as I know though, this div-wrapping method is quite an old way to do things. More modern way is to use React Fragments.

    As for why such rule exists, I’ve always thought about it similarly to JavaScript’s ordinary functions, so something like this makes no sense:

    function func() {
      return 1;
      return 2;
    }
    

    If we really want to return 1 and 2 though, we would wrap them into something such as an array:

    function func() {
      return [1, 2];
    }
    
    Login or Signup to reply.
  2. Wrapping a div is done due to following Reasons :

    • Separates concerns and keeps plugin logic isolated.
    • Avoids conflicts with React’s rendering.
    • Manages lifecycles effectively.
    • Enhances reusability of the plugin and React components.
    • Bridges the gap between jQuery’s imperative nature and React’s declarative nature.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search