Featured Image
Featured Image

React just got ugly — React 16.4 Update

There was a recent update to React, 16.4, that changed how getDerivedStateFromProps works. The main difference is that it's now being called even on state changes instead of, as the name suggests, just on prop changes - which is the way it worked in 16.3.

If you don’t know about getDerivedStateFromProps, it's a static lifecycle method introduced in React 16.3 to prepare for async rendering.

In 16.3 it was proposed as an alternative for componentWillReceiveProps, which is deprecated and will be removed in React 17.

getDerivedStateFromProps is being added as a safer alternative to the legacy componentWillReceiveProps. — React 16.3

In my opinion, the removal of componentWillReceiveProps made it significantly more verbose to write components that have both local state, and also derive part of their state from props.

A fully controlled or fully uncontrolled component won’t have the problems that we’ll see now. So you should stick to these whenever possible, but in contrast to popular belief, we’re often faced with components that are neither fully controlled nor uncontrolled.

Let’s consider the following example. You have a Page component that renders the text of the current page and lets you edit it. So far, Page can be uncontrolled - we keep the text in state and only update it when a button is pressed on the page, triggering an update to the parent component.

Now, let’s add pagination: The parent component has a button that allows you to go to the next page, which will re-render your Page component with a new text prop. This should now discard the local state in the Page component and render the text of the new page instead.

Here’s a codesandbox of the app:

The App:

And here’s the first try to implement the Page component:

The bug

The important thing to point out here is the use of componentWillReceiveProps or getDerivedStateFromProps to update the local text state when the page changed.

But right now, the Page component has a bug (even though it’s not noticeable in the way it is used right now). We reset the state on every re-render. This is because of how componentWillReceiveProps / getStateDerivedFromProps worked:

componentWillReceiveProps: Note that if a parent component causes your component to re-render, this method will be called even if props have not changed. Make sure to compare the current and next values if you only want to handle changes. React Documentation

Now here’s the fun part: Having this (buggy?) code that worked perfectly fine in our example app, doesn’t work anymore in React 16.4 when you’re using getDerivedStateFromProps: The onChange on the textarea triggers a setState which itself triggers a getDerivedStateFromProps which sets the state again to the old text from props. Which means you cannot write in the textarea anymore.

The main problem with this code is that it was not resilient to re-renders. Because of this seemingly breaking change, there’s a huge GitHub issue if this behavior in React 16.4 shouldn’t be considered a breaking change which would require a major version bump.

The solution

The point I’m trying to make is a different one, however. It gets clear when we look at the suggested solution which fixes this bug: As stated above, we need to compare the current and next values of props before setting the state.

This was easy for componentWillReceiveProps:

But not so easy with static getDerivedStateFromProps: It's static and we only receive (props, state) as arguments. To compare props with prevProps we have to save prevProps in state to be able to access it.

That’s ugly

Now take a step back. Think about the simplicity of the app and what we’re trying to achieve: A text field. And then look at the solution code again.
There’s clearly something wrong with React when this is the recommended way to handle such a fundamental use-case in React 16.4. To me, it feels hacky having to save the previous props in the state. There should be an easier way to do that. (You could also (ab)use the key attribute and do a full remount of the Page component avoiding getDerivedStateFromProps. This is described in another article, or this one. But that doesn't feel polished either.)

I sincerely hope that the React team’s initiative to push async rendering doesn’t further come at the cost of React’s usability in everyday scenarios like the one above. React 16.4 feels like a step backward after so many great and useful features in React 16.3. With the removal of componentWillReceiveProps there is no simple way to just listen to props changes anymore or access previous props.

Originally published at cmichel.io

Written by

Full Stack Software Engineer #javascript #EOS. Into Recreational Math / CS 🤯 Just message me about anything, my mind is open.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store