Tomasz Kasperek
Tomasz Kasperek

Reputation: 1187

Refreshing children state from parent React

I have a table with some data and each element in the table is a React class component. It looks like this:

Table

All i want is to have one checkbox for "check all" feature (top left checkbox). The thing is I don't know how to solve that because of props and state.

I have code like that in single element component:

getInitialState: function() {
    return { component: this.props.data };
  },

render: function() {
    var data = this.state.component;
    data = data.set('checked', this.props.data.get('checked'));
    ...
}

And I know I shouldn't get checked param from props but it is just temporary.

What I have problem with is: When I update checked param in parent it doesn't update state, because getInitialState isn't called after refresh (yep, i know it should be like that).

My question is: can I somehow update state of child component? Or it is better way to achieve that.

Upvotes: 8

Views: 40405

Answers (4)

Luca Pelosi
Luca Pelosi

Reputation: 158

With functional components: An easy way to refresh the children internal state when props provided by parent change is through useEffect():

In the children:

const [data, setData] = useState(props.data);

useEffect( () => {
    setData(props.data);
}, [props.data]); 

In this way everytime the props.data change the useEffect will be triggered and force to set a new status for some data and therefore the component will "refresh".

Upvotes: 15

Duray Akar
Duray Akar

Reputation: 21

Please see the react documentation on Lifting State Up. In your child component, you need to use the props. To update the prop, you need to provide an update function from the parent.

Upvotes: 2

edoloughlin
edoloughlin

Reputation: 5891

You can solve this by storing the checked state of all child elements in the parent only. The children set their checked status based on props only (they don't use state for this) and call a callback supplied by the parent to change this.

E.g., in the child:

render: function() {
    //... not showing other components...
        <input type="checkbox"
               value={this.props.value}
               checked={this.props.checked}
               onClick={this.props.onClick}>
}

The parent supplies the onClick, which changes the checked status of the child in its state and passes this back to the child when it re-renders.

In the parent:

getInitialState: function() {
    return {
        allChecked: false,
        childrenChecked: new Array(NUM_CHILDREN) // initialise this somewhere (from props?)
    }
},

render: function() {
    return <div>
               <input type="checkbox" checked={this.state.allChecked}>
               {children.map(function(child, i) {
                   return <Child checked={this.state.childrenChecked[i]}
                                 onClick={function(index) {
                                     return function() {
                                         // update this.state.allChecked and this.state.childrenChecked[index]
                                     }.bind(this)
                                 }.bind(this)(i)}
                          />
                }).bind(this)}
           </div>;
}

-- not checked for typos etc.

Upvotes: 4

Samuli Hakoniemi
Samuli Hakoniemi

Reputation: 19049

My approach is that you should have structure something like this in parent's render:

<ParentView>
{ this.props.rows.map(function(row) {
    <ChildRow props={row.props} />
  }).bind(this)
}
</ParentView>

And then on row.props you have the information whether current row item is checked or not. When parent checkbox is toggled, you populate all the row.props with the status.

On child, you will receive those with componentWillReceiveProps and you do the magic (e.g. set the correct state) when checkbox is toggled:

componentWillReceiveProps: function(props) {
  this.setState({isChecked: props.isChecked});
}

(Info from the React's docs: Calling this.setState() within this function will not trigger an additional render.)

Child element's render would be something like:

<div>
  <input type='checkbox' checked={this.state.isChecked} />
  <label>{this.props.label}</label>
</div>

Upvotes: 8

Related Questions