Search code examples
coding-stylereactjsencapsulation

Encapsulation with React child components


How should one access state (just state, not the React State) of child components in React?

I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.

Here's a simplified version of my code

OptionsChooser = React.createClass({
  //function passed to Modal, called when user "OK's" their new selection
  optionsSelected: function() {
    var optsSelected = this.refs.modal.getOptionsSelected();
    //setState locally and save to server...
  },

  render: function() {
    return (
      <UneditableOptions />
      <button onClick={this.showModal}>Select options</button>
      <div>
        <Modal 
          ref="modal" 
          options={this.state.options} 
          optionsSelected={this.optionsSelected} 
        />
      </div>
      );
  }
});

Modal = React.createClass({
  getOptionsSelected: function() {
    return $(React.findDOMNode(this.refs.optionsselector))
      .find('input[type="checkbox"]:checked').map(function(i, input){
        return {
          normalisedName: input.value
        };
      }
     );
   },

  render: function() {
    return (
      //Modal with list of checkboxes, dismissing calls optionsSelected function passed in
    );
  }
});

This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?


Solution

  • Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:

    OptionsChooser = React.createClass({
      onOptionSelect: function(data) {
    
      },
    
      render: function() {
        return <Modal onClose={this.onOptionSelect} />
      }
    });
    
    Modal = React.createClass({
    
      onClose: function() {
        var selectedOptions = this.state.selectedOptions;
        this.props.onClose(selectedOptions);
      },
    
      render: function() {
        return ();
      }
    });
    

    I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.

    Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.