The UpgradeJS Blog

Why and How to Upgrade React From v16.x to v18.x

At OmbuLabs opens a new window and UpgradeJS opens a new window we love using React opens a new window to create dynamic and scalable user interfaces on the front-end of web, mobile, and desktop applications.

With the release of React v18.0 opens a new window , several new features and enhancements have been added that can improve the development experience and boost the performance of React applications.

In this post, we will provide a guide to upgrading your React application from version 16.x to version 18.x, so that you can take advantage of the latest features and improvements.

It is worth noting that React version 16.x was a significant release that introduced several crucial features, including hooks opens a new window , which made it easier for developers to reuse code and manage components’ behavior.

As a result, many projects still use version 16.x, making it one of the most widely used versions opens a new window of React today.

Why the upgrade matters

There are several reasons why you should consider upgrading your React application to version 18. Let’s take a look at some of them:

Performance improvements

The upgrade to version 18.x of React can result in significant performance improvements, including enhanced memory usage opens a new window , positively impacting your application’s user experience.

Bug fixes

Upgrading React can resolve bugs and issues in previous versions, enhancing your application’s stability and reliability. You can find a list of bug fixes introduced in React 18.0 in the GitHub release notes opens a new window .

New features

As React introduces new versions, they may include additional features that can boost your application’s functionality. For example, React 18 introduced automatic batching opens a new window , a performance enhancement that minimizes the number of renders executed.

Embrace new features and changes

Keeping your application up-to-date allows you to be prepared for upcoming features and changes. In version 18, concurrent rendering was introduced.

Although concurrency opens a new window is not a standalone feature, it is a crucial mechanism that paves the way for the implementation of new features built on top of it.

Developer experience enhancement

The upgrade to the version 18.x of React can lead to enhancements in the developer experience, facilitating more efficient work with React.

For instance, the introduction of the useId hook opens a new window in React 18.0 streamlines generating unique IDs.

This new feature can potentially replace the need for external libraries like uuid opens a new window for this purpose.

Reduces technical debt

Upgrading to the latest version of React reduces technical debt opens a new window , which refers to the accumulation of outdated or unnecessary code that may hinder development and complicate maintenance.

By keeping your codebase up-to-date, you ensure its cleanliness and efficiency, facilitating future maintenance and updates.

Key changes and resources

To learn about the changes introduced in React versions 17.x and 18.x, visit the official GitHub releases page opens a new window . You’ll find detailed information on updates, improvements, bug fixes, performance enhancements, and new features for both version 17.0 opens a new window and 18.0 opens a new window .

Reviewing these pages will help you better understand the updates and any potential compatibility issues when upgrading. GitHub releases page is an excellent resource for developers using React and those interested in learning more about the technology.

Upgrade approaches

In this section, we will explore different approaches to upgrading your React applications and discuss their pros and cons.

"All-or-nothing"

To upgrade a React application, the most widely used strategy is the “all-or-nothing” approach, which involves upgrading the entire application at once.

However, there are a few ways to perform the upgrade. One option is to upgrade React step by step, one major version at a time. This can be helpful in identifying any issues with each upgrade and addressing them before moving on to the next version.

Another approach is to upgrade directly to the latest version. This can have some benefits, such as faster access to new features and easier maintenance.

Before deciding to upgrade in this way, it’s important to carefully consider the potential risks and drawbacks, such as compatibility issues and an increased risk of bugs.

It is worth noting that React maintainers have minimized the breaking changes opens a new window in version 17.0. Therefore, you may try to perform the upgrade of your application directly to version 18.x with less concern.

Also, before using the new rendering API in version 18.x, your application will continue to work opens a new window in React 17 mode.

Gradual upgrade

When upgrading your React application to version 18.x, you now have the option of using a gradual upgrade opens a new window strategy, in addition to the traditional all-or-nothing approach.

With gradual upgrade, you can upgrade your application piece by piece, which can make the process smoother and less disruptive.

If your current React version is less than 17.x, you’ll need to upgrade to version 17.0 first before you can use gradual upgrade. This upgrade will involve upgrading the entire application.

However, if your current React version is greater than or equal to 17.0, you can choose between the all-or-nothing or gradual upgrade approach.

To use gradual upgrade, you’ll need to add an extra configuration to your application. React maintainers have created a demo opens a new window that shows how to configure a build system to serve two different versions of React side by side in the same app.

The demo includes a helpful function called lazyLegacyRoot opens a new window , which allows you to import legacy components into upgraded modern components. We recommend taking a closer look at this demo, as it includes some interesting tricks that may not be immediately obvious.

Worth noting that this approach makes sense to consider for large apps that are hard to maintain. Loading two versions of React is still not ideal (even if one of them is loaded lazily on demand). One of the big advantages is that it can reduce the risk associated with the upgrade project opens a new window .

Backwards-compatible changes first

When upgrading your React application, consider taking a “backwards-compatible” approach. This involves identifying changes that can work with both your current version of React and the version you want to upgrade to.

By taking this approach, you can isolate small parts of your application and validate them using your tests. This way, you can deploy these upgraded components independently from the entire React upgrade, which can make the process smoother and less risky.

This technique can also be applied to upgrading dependencies, as long as the dependencies are compatible with both your current and desired versions of React.

An example of a “backwards-compatible” approach our case study described in the An example of the upgrade section.

Test coverage

Before upgrading a React application, one of the most critical things to consider is test coverage. It’s important to cover your application with at least end-to-end tests, focusing on the main parts and flows of your application. This will be particularly helpful during the upgrade process.

We also recommend covering your application with unit tests before the upgrade. This step can help identify bugs in your components and be beneficial during the “backwards-compatible” approach described in the Backwards-compatible changes first section.

How to upgrade

When it comes to upgrading a React application, we are fortunate to have access to amazing documentation provided by the React maintainers.

In addition to comprehensive learning materials opens a new window and an excellent API reference opens a new window , React also has a blog opens a new window where you can find a wealth of interesting and useful information about the library.

This includes articles that can help you solve problems and highlight best practices, as well as news and announcements. If you’re looking to upgrade to React version 18.x, there’s an article opens a new window that can help you do just that.

If you’re upgrading to version 17.x, there’s a release article opens a new window for this version that provides possible solutions to any issues you may encounter during the upgrade from version 16.x.

There’s no need to duplicate content from the official articles, as all the pieces are essential and worth considering. However, there is an interesting case study on how the upgrade to React 18.x can look like in the An example of the upgrade section.

It’s important to note that version 17.0 includes a significant change: the componentWillMount, componentWillReceiveProps, and componentWillUpdate lifecycle methods will no longer work.

You can still use them, but you’ll need to replace them with their aliases. The UNSAFE_* aliases were introduced in version 16.3 to allow developers to gradually migrate from the original methods.

You can learn more about this migration in the Update on Async Rendering opens a new window article.

To address this change, you can run a “codemod opens a new window ” - npx react-codemod rename-unsafe-lifecycles - in the root of your React application.

This script will rename all the required methods to their aliases with the prefix UNSAFE_. You can find more useful codemods in the official React project opens a new window on GitHub.

An example of the upgrade

If you started developing your application after the introduction of hooks and wrote it using only function-based components, you can expect fewer troubles opens a new window when upgrading React. However, unfortunately for maintainers of legacy applications written with class-based components, the upgrade process can be more complex and complicated.

To prepare your application for the new features of React, it is necessary to eliminate side effects in the render phase lifecycle methods. This is because the new concurrent mode in React changes the way the render phase works, making it interruptible opens a new window .

From this point, the only safe place to perform side effects is the commit phase.

React lifecycle flow

You can find a comprehensive explanation of concurrency in React in the official article about React v18.0 opens a new window , and a description of how it affects lifecycle methods in the Strict Mode opens a new window official article.

Additionally, we have an article that explains how the Strict Mode can help identify problems with your useEffect’s opens a new window .

As an example let’s consider the UNSAFE_componentWillReceiveProps (or componentWillReceiveProps) lifecycle method, which runs during the render phase. Here is a quick sample:

performSideEffect code sample

As you can see from the code sample, the performSideEffect function is executed within the UNSAFE_componentWillReceiveProps method of the <Child /> component. This method is called when the component receives new props.

Specifically, the function is executed only if the number property in the next props is less than 4.

The outcome of this execution will be as follows:

Lifecycle flow with changes of the number property captured in console logs

In this case, in order to achieve the same end result using componentDidUpdate in your React application upgrade, you will need to make the following changes:

  UNSAFE_componentWillReceiveProps(nextProps) {
    /* … */
    // if (nextProps.number < 4) {
      // this.props.performSideEffect(nextProps.number);
    // }
  }

  componentDidUpdate(prevProps) {
    /* … */
    if (this.props.number < 4) {
      prevProps.performSideEffect(this.props.number);
    }
  }

Here is the result of our changes:

Lifecycle flow with changes of the number property captured in console logs after migration to componentDidUpdate

In the given screenshots, it’s clear that the printed numbers and the amount of render cycles are the same for both versions. But when looking at the code and method changes, the component’s logic is now more complicated, less clear, and requires changes in not just the lifecycle method but also in other methods called from the new lifecycle method.

You’ll also see that the order in which side effects are executed has changed. While the numbers from props and render cycles are the same for this particular component, this change in side effect order might lead to unexpected bugs or issues in components that depend on the execution order of side effects and their results.

Now let’s look at another modification. To minimize unnecessary renders in React apps, developers often use the shouldComponentUpdate method. We’ll adjust the current implementation and see how it affects the component’s flow and render cycles.

  shouldComponentUpdate(nextProps) {
    /* … */
    // return true;
    return nextProps.number < 2;
  }

As a result, we will obtain the following output from the modified example that uses UNSAFE_componentWillReceiveProps:

Lifecycle flow with changes of the number property captured in console logs after changes in the shouldComponentUpdate

And the following output will be produced in an example that uses componentDidUpdate:

Lifecycle flow with changes of the number property captured in console logs after migration to componentDidUpdate and with changes in the shouldComponentUpdate

The differences in these screenshots effectively demonstrate that migrating from UNSAFE_componentWillReceiveProps to componentDidUpdate may not be as straightforward as anticipated, and can even lead to unexpected outcomes in some cases.

Conclusion

Upgrading a React application can sometimes be quite challenging. However, on the horizon, there are new React features that can significantly improve aspects like performance, stability, developer experience, and enhance your application’s functionality.

It’s definitely worth giving them a try. As soon as you begin planning your application’s upgrade, you’ll be one step closer to unlocking these great opportunities!

Need help upgrading to React 17 or 18? We can help! We provide consulting services specialized in tech debt and upgrading legacy React applications opens a new window !