In this post, i'll explain how to pass a state between components in Reactjs.
We'll build a small 'How many books did you read?' app, in this application, we
have two main components one big called 'Library' and another small 'Book', we have 3 books in the library state and each book has its own state. check any book to count as read. try the app here
Let's begin with code:
Passing state from parent to child
In our Library component we have this state
this.state = {
reads: 0,
books: [
{
name: 'Zero to one',
isbn: '9780804139298',
author: 'Peter Thiel',
cover: 'https://images.gr-assets.com/books/1414347376l/18050143.jpg',
status: false
},
{
name: "The Manager's Path",
isbn: '9781491973899',
author: 'Camille Fournier',
cover: 'https://images.gr-assets.com/books/1484107737l/33369254.jpg',
status: false
},
{
name: 'Calculus, Better Explained',
isbn: '9781470070700',
author: 'Kalid Azad',
cover: 'https://images.gr-assets.com/books/1448590460l/27993945.jpg',
status: false
}
]
};
and want to create this.state.books.length
- as a number - Book
components each have props from the books
array of the Library
component's state. We have to deal with the two components.
First with the parent, we have to create the Book
component this.state.books.length
- as a number - times, and pass our diffrent values to them Like this:
{
this.state.books.map((_book, _id) => {
return (
<Book
handleCounter={this.handleCounter}
key={_id}
id={_book.isbn}
name={_book.name}
isbn={_book.isbn}
author={_book.author}
cover={_book.cover}
/>
);
});
}
Note ignore handleCounter
for now.
Second with the child Book
, we have the values from parent, let's use them:
...
render() {
return (
<Card>
<Image
src={this.props.cover}
alt="Book cover"
...
Until now we created 3 Book
components from the parent component Library
, and set their values from the parent state.
Easy! Right?
Great, let's begin the second part
Passing state from child to parent
In this section, we want to handle the number of books you read by checking on each book checkbox.
In our Book
we have this state
this.state = {
status: false,
id: this.props.id
};
Note don't forget to pass props
to component's constructor.
status
means if you read this book or not, and its default value is false
, id
is the id of this book and i set it by book id like we learned in the previous section.
we need to handle change of this status then update the books array in the parent state.
In our Book
component, we'll add a checkbox that recieve the change of the book status and pass this.handleChange
to its onChange
event like this:
<input type="checkbox" name="example" onChange={this.handleChange} />
you need to bind the function first, then update the Book
state with the new status, after updating the child state we'll update the state of the parent Library
like this:
handleChange() {
this.setState({status: !this.state.status}, this.updateLibraryCount);
}
updateLibraryCount() {
this.props.handleCounter(this.state);
}
In updateLibraryCount
we used handleCounter
function of the Library
as a prop, then passed the Book
state to it, Now the Library
sees the Book
state, Great! let's use it.
handleCounter(_State) {
//Get the index of this book by searching by its unique isbn
const ObjNum = this.state.books.findIndex(
_book => _book.isbn === _State.id
);
//then update its value in the Library component
this.setState(
{
books: update(
ObjNum,
{...this.state.books[ObjNum], status: _State.status},
this.state.books
)
},
() => {
//this is a callback to handle the new change of the book status and increment the reads
const _read = this.state.books.filter(_book => _book.status === true)
.length;
this.setState({reads: _read});
}
);
}
I hope you're understand how to pass the state from the parent to child and vice versa, Here's the full code, and this is the original post on my blog. If you have questions, feel free to ask in comments or email.
Top comments (7)
salem
nice explanation but it's not recommended to use :
this.setState({status: !this.state.status}, this.updateLibraryCount);
|.|
|_|
V
this.setState({status: !this.state.status} you better use
this.setState((prevState) => {
return { status: !prevState.status }
})
Why?
setState happens asynchronously... Therefore you need to use previous state first to ensure you are using the latest if you are going to calculate the state based on previous state.
Yes this is it
Thank you
Hello Zeyad,
thanks for this state tutorial!
I was asking myself several questions while reading it though. I hope you can enlighten me :)
Why do you keep the book ID in the
<Book>
component state ? Wouldn't it be prettier to pass thebook.id
prop when you callthis.props.handleCounter
inupdateLibraryCount
? To me, the ID should not be modified, so we don't have to put it in the state.Is there any particular reason why you don't pass a function as the first argument of this?
this.setState({status: !this.state.status}, this.updateLibraryCount);
Thanks again!
See you :)
Thanks Thomas, to your first question, you're right it'll be prettier, you're welcome to contribute and update this on GitHub repo.
To your second question, from Reactjs documentation
and this what i want.
I was talking about the first parameter :)