A door, controlled by a key, from here

Three approaches to distribute the state across components in React

Rodrigo Pombo
Hexacta Engineering
3 min readDec 28, 2017

--

I was working on a typical CRUD screen when I asked myself for the hundredth time: “Should I keep the state in this component or move it to the parent?”.

This time it was a component that needed slight control over a child state. You probably faced the same problem too.

Let’s review it with a simple example together with three approaches to fix it. The first two approaches are common practice, the third one is less conventional.

The Problem

To show you what I mean, I’ll use a simple book CRUD screen (so simple it doesn’t have create and delete operations).

UI for updating books

We have three components. <BookList/> is a component that shows a list of books and buttons to edit them. <BookForm/> which has two inputs and a button to save the changes to a book. And the <BookApp/> that contains the other two components.

So, what state do we have? Well, <BookApp/> should keep track of the list of books and something to identify the book that it’s currently being edited. <BookList/> doesn’t have any state. And <BookForm/> should keep the current state of the inputs until the “Save” button is clicked.

Try it on codesandbox

Looks good to me, but there’s a problem: it doesn’t work.

We are initializing the <BookForm/> state when the instance of the component is created, so, when another book is selected from the list the parent has no way of letting it know that it need to change it.

So how do we fix it?

Approach 1: Controlled Component

One common approach is to lift the state up, converting <BookForm/> into a controlled component. We remove <BookForm/> state, add activeBook to <BookApp/> state and add an onChange prop to <BookForm/> which we call every time there’s a change on any input.

Try it on codesandbox

Now it works, but to me it doesn’t feel right to lift the state of the <BookForm/>. <BookApp/> doesn’t care of any changes to the book until the user clicks “Save”, so why does it need to keep it on its own state?

Approach 2: Synchronizing States

Another option is keeping the state in <BookForm/> but synchronize it with the book prop whenever it changes using componentWillReceiveProps.

Try it on codesandbox

This approach is often considered a bad practice because it goes against React’s idea of having a single source of truth. I’m not sure this is the case, however, synchronizing state is not always easy. Also, I try to avoid using lifecycle methods whenever I can.

Approach 3: Component Controlled by Key

But why are we trying to recycling the old state? Wouldn’t it make sense to have a new instance with a brand new state every time the user selects a book?

To do that, we need to tell React to stop using the old <BookForm/> instance and create a new one. That’s what the key prop is for.

Try it on codesandbox

If an element has a different key from the last render, React creates a new instance for it. So, when the user selects a new book, the key of <BookForm/> changes, a new instance of the component is created and the state is initialized from the props.

What’s the catch? Reusing component instances means less mutations to the DOM which means better performance. So, when we force React to create a new instance of the component we get some overhead for the extra DOM mutations. But this overhead is minimal for cases like this, where the key is not changing too fast and the component is not big.

Thanks for reading.

--

--