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!
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 .
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With