Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the state of child checkbox components within a parent ListView component using React-Native?

I have a list of options with check boxes and a done button inside a parent ListView. When the done button is pressed, I want to know which of the check boxes are checked.

I should add that I have tried to maintain an array of the checked boxes in the ListView using callback functions from ChildCheckBox. It worked fine except when navigating back to the ListView, the array would be reset while the check boxes still appeared to be checked. I would prefer to have the onDonePress() function just query which boxes are checked then respond accordingly at that time rather than rely on the ListView maintaining a an array.

Here is the ListView:

class ParentListView extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  }

  componentDidMount() {
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(ROW_DATA),
    });
  }

  onCheckPress() {
    console.log('Check Pressed')
    // callback from ChildCheckBoxCell...?
  }

  onDonePress() {
    console.log('Done pressed')
    // callback from ChildDoneCell...?
  }

  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderRow.bind(this)}
        style={styles.listView}
        />
    );
  }

  renderRow(cell) {
    if (cell.type === 'ChildCheckBoxCell') {
      return (
        <ChildCheckBoxCell onChange={() => this.onCheckPress()} />
      );
    }

    if (cell.type === 'ChildDoneCell') {
      return (
        <ChildDoneCell onDonePress={() => this.onDonePress()}/>
      );
    }
  }
}

And here is the ChildCheckBoxCell component:

class ChildCheckBoxCell extends Component {

constructor(props) {
    super(props);
    this.state = {
      isChecked: false,
    };
  }

  onChange() {
    this.setState({isChecked: !this.state.isChecked});
    //Callback...
    this.props.onChange();
  }

  render() {
    return (
      <TouchableHighlight onPress={() => this.onChange()}>
        <Text>{this.state.isChecked? 'Checked' : 'UnChecked'}</Text>
      </TouchableHighlight>
    );
  }
}

And finally, here is the ChildDoneCell component

class ChildDoneCell extends Component {

  onDonePress() {
    //Callback...
    this.props.onDonePress();
  }

  render() {
    return (
      <TouchableHighlight onPress={() => this.onDonePress()}>
        <Text>DONE</Text>
      </TouchableHighlight>
    );
  }
}

Thanks in advance!

like image 679
Michael Campsall Avatar asked May 16 '16 01:05

Michael Campsall


People also ask

How do you get the value from child component to parent component?

To pass data from child to parent component in React:Pass a function as a prop to the Child component. Call the function in the Child component and pass the data as arguments. Access the data in the function in the Parent .

How do you pass state from parent component to child?

First, click on App and observe its state under the Hooks section on the right pane. Second, click on a given player component and examine its props. Finally, click on any of the items in the page and see how the state and props of the parent and child components are updated, respectively.

How can child components communicate with the parent component?

In the parent component, create a callback function. This callback function will retrieve the data from the child component. Pass the callback function to the child as a props from the parent component. The child component calls the parent callback function using props and passes the data to the parent component.

How do I get child props from components?

To pass data from a child component to its parent, we can call a parent function from the child component with arguments. The parent function can be passed down to the child as a prop, and the function arguments are the data that the parent will receive.


1 Answers

Here's what you should do. I included comments in the code to explain. There should be 6 steps.

class ParentListView extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  }

  componentDidMount() {
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(ROW_DATA),
    });
  }

  // 1. Change your callback functions to class properties
  // this way it is auto-bound to this class instance and you don't bind it during render, which
  // creates rendering overhead. Notice how the selected `cell` is
  // passed in here. It will be explained in the last steps how that happens.
  onCheckPress = (cell) => {
    // Update the `isChecked` state of this cell and update
    // your `ListView.DataSource` with it
    console.log('Check Pressed', cell);
    // callback from ChildCheckBoxCell...?
  };

  // 2. Do the same thing here as step 1.
  onDonePress = (cell) => {
    console.log('Done pressed', cell);
    // callback from ChildDoneCell...?
  }

  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderRow.bind(this)}
        style={styles.listView}
        />
    );
  }

  renderRow(cell) {
    if (cell.type === 'ChildCheckBoxCell') {
      return (
        // 3. You should pass in the cell data down here AND you should
        // pass a reference to your callback
        <ChildCheckBoxCell cell={cell} onChange={this.onCheckPress} />
      );
    }

    if (cell.type === 'ChildDoneCell') {
      // 4. Do the same thing here, except change the reference of 
      // the callback to the other callback, obviously
      return (
        <ChildDoneCell cell={cell} onDonePress={this.onDonePress}/>
      );
    }
  }
}

class ChildCheckBoxCell extends Component {

  render() {
    return (
      // 5. Dereference the function `onChange` and bind it to your
      // `cell` object, don't worry about `null` changing your 
      // `this` context, it won't. This is how the `cell` object is
      // passed an argument in the method on step 1.
      <TouchableHighlight onPress={this.props.onChange.bind(null, this.props.cell)}>
        {/* Stop using `state` to keep track of `isChecked`, you'll
            lose this state if this ever is torn down and re-rendered
            from the parent component */}
        <Text>{this.props.cell.isChecked? 'Checked' : 'UnChecked'}</Text>
      </TouchableHighlight>
    );
  }
}

class ChildDoneCell extends Component {

  render() {
    return (
      // 6. This is the same thing as step 5.
      <TouchableHighlight onPress={this.props.onDonePress.bind(null, this.props.cell)}>
        <Text>DONE</Text>
      </TouchableHighlight>
    );
  }
}

You will notice that you could bind the cell data in the renderRow function, but it is not preferred. The rule of thumb to follow is that you want to dereference your callback function to your child data as far down as possible for performance reasons, and because it is better to be explicit for maintenance reasons. This is the shortcut alternative:

// ParentListView
renderRow(cell) {
  if (cell.type === 'ChildCheckBoxCell') {
    return (
      // No more passing of cell, but now I'm binding it with the
      // cell object
      <ChildCheckBoxCell onChange={this.props.onCheckPress.bind(null, cell)} />
    );
  }

  if (cell.type === 'ChildDoneCell') {
    // Same thing
    return (
      <ChildDoneCell onDonePress={this.onDonePress.bind(null, cell)}/>
    );
  }
}

// ChildCheckBoxCell

render() {
  return (
    // Notice how you lose the explicitness of your callback
    // function and have no idea that this function implicitly passes
    // the `cell` object because you prematurely bound it with the `cell` object
    <TouchableHighlight onPress={this.props.onChange}>
      <Text>{this.props.isChecked? 'Checked' : 'UnChecked'}</Text>
    </TouchableHighlight>
  );
}

EDIT

I updated the code to make more sense and to get rid of unnecessary instance methods. I would highly suggest that you get rid of the state in your ChildCheckBoxCell and try to feed it down via props as part of your cell object in my first example.

like image 145
rclai Avatar answered Oct 12 '22 02:10

rclai