loading...

How to pass state between components in reactjs

zeyadetman profile image Zeyad Etman ・3 min read

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:

Full Code here

{
    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:

Full code here

...
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

Full code here

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:

Full code here

<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:

Full code here

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.

Full Code here

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.

Discussion

pic
Editor guide
Collapse
uranium93 profile image
uranium93

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 }
})

Collapse
zeyadetman profile image
Collapse
chiangs profile image
Stephen E. Chiang

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.

Thread Thread
uranium93 profile image
uranium93

Yes this is it
Thank you

Collapse
keabard profile image
Thomas Simonnet

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 the book.id prop when you call this.props.handleCounter in updateLibraryCount? 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 :)

Collapse
zeyadetman profile image
Zeyad Etman Author

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

"The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered."

and this what i want.

Collapse
keabard profile image
Thomas Simonnet

I was talking about the first parameter :)