As WordPress developers, we often integrate custom React components into our themes and plugins to create dynamic and responsive user interfaces.

With the upcoming release of React 19, it’s crucial to prepare for changes and deprecations that could impact our existing codebases. WordPress 6.6, which will be released on July 16, includes React 18.3. This version is nearly identical to 18.2 but adds warnings for deprecated features to help you prepare for React 19.

Addressing these deprecations is essential to ensure compatibility with React 19, and ignoring them can lead to bugs or issues in your custom blocks, plugins, or themes when React 19 is released and included in WordPress.

This article outlines each deprecation, provides code examples, and guides you through replacing deprecated features to maintain smooth functionality.

Removed deprecations in React

Several deprecated APIs and features have been removed to streamline the React library and encourage best practices. This section covers the key changes and how to update your code accordingly.

1. Removal of defaultProps for function components

React 19 will remove defaultProps for function components in favor of ES6 default parameters. According to the WordPress team, this deprecation is most commonly used in plugins and themes.

As a WordPress developer, you might use defaultProps to provide default values for props in your function components, ensuring that components behave correctly even if certain props are not passed.

Here is what your current code may look like with defaultProps:

function CustomButton({ label, color }) {
    return <button style={{ backgroundColor: color }}>{ label }</button>;
}

CustomButton.defaultProps = {
    label: 'Click me',
    color: 'blue',
};

In this example, a CustomButton component has default label and color values provided by defaultProps. With React 19, this will throw a warning error, urging you to use ES6 default parameters instead.

Here is the updated code with ES6 default parameters:

function CustomButton({ label = 'Click me', color = 'blue' }) {
    return <button style={{ backgroundColor: color }}>{ label }</button>;
}

Using ES6 default parameters, the default values are now directly in the function signature, making the code easier to read and maintain.

2. Removal of propTypes for function components

propTypes was deprecated in React 15.5.0 and will also be completely removed in the React package in v19. If you’re using propTypes, it’s recommended that you migrate to TypeScript or another type-checking solution.

You might have been using propTypes to validate the props passed to your function components to ensure they receive the correct types and values. For example:

import PropTypes from 'prop-types';

function CustomButton({ label, color }) {
    return <button style={{ backgroundColor: color }}>{ label }</button>;
}

CustomButton.defaultProps = {
    label: 'Click me',
    color: 'blue',
};

CustomButton.propTypes = {
    label: PropTypes.string,
    color: PropTypes.string,
};

Today, you can start using TypeScript for these type-checkings:

type CustomButtonProps = {
    label?: string;
    color?: string;
};

const CustomButton = ({ label = 'Click me', color = 'blue' }: CustomButtonProps) => {
    return <button style={{ backgroundColor: color }}>{ label }</button>;
};

3. Removal of Legacy Context (contextTypes and getChildContext)

Given the longstanding nature of many plugins and codebases in WordPress, you might still be using the legacy contextTypes and getChildContext APIs in your class components. These APIs were used to pass data from a parent component to its descendants without explicitly passing props at each level.

However, it’s important to note that Legacy Context was deprecated in React 16.6.0 and will be removed in React v19. This change is intended to make React slightly smaller and faster, as the Legacy Context API had subtle bugs that were often easy to overlook.

The legacy method has been replaced with the new contextType API.

Here’s an example of how you may be using the deprecated Context API in a WordPress plugin to pass global settings, such as the site title, from a parent component to a child component without prop drilling:

import PropTypes from 'prop-types';

class SettingsProvider extends React.Component {
  static childContextTypes = {
    siteTitle: PropTypes.string.isRequired,
  };

  getChildContext() {
    return { siteTitle: 'My WordPress Site' };
  }

  render() {
    return <SettingsConsumer />;
  }
}

class SettingsConsumer extends React.Component {
  static contextTypes = {
    siteTitle: PropTypes.string.isRequired,
  };

  render() {
    return <div>Site Title: {this.context.siteTitle}</div>;
  }
}

In contrast, the modern approach uses the createContext method. This is the method you should adopt as you prepare for React 19:

import React from 'react';

const SettingsContext = React.createContext();

class SettingsProvider extends React.Component {
  render() {
    return (
      <SettingsContext value={{ siteTitle: 'My WordPress Site' }}>
        <SettingsConsumer />
      </SettingsContext>
    );
  }
}

class SettingsConsumer extends React.Component {
  static contextType = SettingsContext;

  render() {
    const { siteTitle } = this.context;
    return <div>Site Title: { siteTitle }</div>;
  }
}

4. Removal of string refs

Using string refs was once a common way to access a DOM element in React components. However, they have been considered legacy since React 16.3.0 and will be removed in v19.

While string refs were straightforward, they had several issues, such as potential naming conflicts and a lack of flexibility.

Consider an example of using string refs in a WordPress custom block. Imagine you have a custom Gutenberg block that includes an input field, and you want the input field to be focused automatically when the block is added to the editor. Here’s how you might have done this using string refs:

class CustomBlock extends React.Component {
  componentDidMount() {
    this.refs.input.focus();
  }

  render() {
    return <input ref="input" placeholder="Enter text..." />;
  }
}

To prepare for React 19, you must replace string refs with callback refs or the React.createRef API. Here’s the same example using a callback ref:

class CustomBlock extends React.Component {
  componentDidMount() {
    this.input.focus();
  }

  render() {
    return <input ref={(input) => (this.input = input)} placeholder="Enter text..." />;
  }
}

5. Removal of module pattern factories

Another deprecated feature that will be removed in React 19 is module pattern factories. This pattern was rarely used and caused React to be slightly larger and slower than necessary.

Module pattern factories allowed developers to create components less conventionally. Here’s an example of how you may be using it:

function SettingsPanelFactory() {
  return {
    render() {
      return (
        <div className="settings-panel">
          <h2>Settings</h2>
          {/* other settings UI components */}
        </div>
      );
    }
  };
}

In this pattern, SettingsPanelFactory returns an object using a render method rather than returning JSX directly.

To comply with React 19, you must migrate module pattern factories to regular functions that return JSX directly. Here’s the updated example:

function SettingsPanel() {
  return (
    <div className="settings-panel">
      <h2>Settings</h2>
      {/* other settings UI components */}
    </div>
  );
}

6. Removal of createFactory API

In React 19, React.createFactory is being removed. This method was more commonly used before JSX became widely supported. It allowed developers to create React elements without using JSX syntax.

However, with the prevalence of JSX, createFactory has become obsolete and can be replaced with simpler, more readable JSX code.

Here’s an example of using createFactory to create a button element. This might be part of a custom WordPress plugin that dynamically generates button elements based on user input:

import { createFactory } from 'react';

const button = createFactory('button');

function CustomButton() {
  return button({ className: 'custom-button', type: 'button' }, 'Click Me');
}

To update this code for React 19, replace createFactory with JSX. This change makes the code more modern, readable, and maintainable:

function CustomButton() {
  return <button className="custom-button" type="button">Click Me</button>;
}

7. Removal of react-test-renderer/shallow

React 19 removes react-test-renderer/shallow to streamline the testing utilities and encourage best practices. In React 18, react-test-renderer/shallow was updated to re-export react-shallow-renderer.

Previously, you might have used react-test-renderer/shallow to create shallow render tests for your React components:

import ShallowRenderer from 'react-test-renderer/shallow';

test('MyComponent shallow render', () => {
  const renderer = new ShallowRenderer();
  renderer.render(<MyComponent />);
  const result = renderer.getRenderOutput();
  expect(result.type).toBe('div');
});

To comply with React 19, you need to install react-shallow-renderer:

npm install react-shallow-renderer --save-dev

And update your import:

import ShallowRenderer from 'react-shallow-renderer';

test('MyComponent shallow render', () => {
  const renderer = new ShallowRenderer();
  renderer.render(<MyComponent />);
  const result = renderer.getRenderOutput();
  expect(result.type).toBe('div');
});

The React team recommends migrating to the React Testing Library, which provides more robust testing practices by focusing on how users interact with your components.

To do this, install the @testing-library/react library as a dev dependency:

npm install @testing-library/react --save-dev

Next, you can test the same component using this modern approach:

import { render, screen } from '@testing-library/react';
import MyBlock from './MyBlock';

test('MyBlock renders correctly', () => {
  render(<MyBlock />);
  const element = screen.getByText('MyBlock content');
  expect(element).toBeInTheDocument();
});

Removed deprecations in React DOM

React DOM has also changed in React 19, with certain deprecated methods removed. This section outlines these changes and guides you through updating your DOM-related code.

1. Removal of react-dom/test-utils API

The react-dom/test-utils API will also be removed in React 19. This impacts how we write tests for our React components. Specifically, the act utility has been moved from react-dom/test-utils to the react package.

Additionally, most other utilities from react-dom/test-utils have been removed. Here’s how to adapt your tests to comply with these changes.

The act utility is essential for ensuring that all updates related to your tests have been processed and applied to the DOM. In React 19, you should import act directly from react instead of react-dom/test-utils.

// Before
import { act } from 'react-dom/test-utils';

// Now
import { act } from 'react';

The React team also recommends migrating your tests to the React Testing Library for a modern and well-supported testing experience. Here are some common use cases and how to update them.

The renderIntoDocument utility will be removed. You can replace it with render from @testing-library/react.

// Before
import { renderIntoDocument } from 'react-dom/test-utils';

renderIntoDocument(<Component />);

// Now
import { render } from '@testing-library/react';

render(<Component />);

Similarly, the Simulate utility for simulating events will be removed. Instead, you should use fireEvent from @testing-library/react, which dispatches an actual event on the element.

// Before
import { Simulate } from 'react-dom/test-utils';

const element = document.querySelector('button');
Simulate.click(element);

// Now
import { fireEvent } from '@testing-library/react';

const element = document.querySelector('button');
fireEvent.click(element);

Be aware that fireEvent dispatches a real event, which means it interacts with the element more naturally than the synthetic events created by Simulate. To properly understand the React testing library, read its documentation.

2. Removal of findDOMNode API

Another significant change coming to React 19 is the removal of ReactDOM.findDOMNode, which was deprecated in React 16.6.0.

This function was used to access the underlying DOM node of a React component, but it had several drawbacks, such as being slow to execute, fragile to refactoring, and breaking abstraction levels.

Instead, you should use DOM refs, which provide a more reliable and efficient way to interact with DOM elements in your React components.

Here’s an example of using findDOMNode to select the text in an input field when the component mounts:

import { findDOMNode } from 'react-dom';

function AutoselectingInput() {
  useEffect(() => {
    const input = findDOMNode(this);
    input.select()
  }, []);

  render() {
    return <input defaultValue="Hello" />;
  }
}

To update this code for React 19, replace findDOMNode with a ref. This change makes the code more robust and aligns it with modern React practices:

import React, { useEffect, useRef } from 'react';

function AutoselectingInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.select();
  }, []);

  return <input ref={inputRef} defaultValue="Hello" />;
}

3. Removal of render API

With React 19, ReactDOM.render will be removed. This method was deprecated in React 18.0.0 in favor of createRoot API from react-dom/client, which provides a more efficient and modern way to initialize and render React applications. This change is part of React’s ongoing effort to streamline and optimize the library.

In a typical WordPress setup, you might have a custom block or a plugin that initializes a React app when the DOM is ready. Previously, you would use ReactDOM.render:

import { render } from 'react-dom';
render(<App />, document.getElementById('root'));

With React 19, you should use createRoot to initialize and render your React application:

import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

4. Removal of unmountComponentAtNode API

React 19 also removes the ReactDOM.unmountComponentAtNode method, which was deprecated in React 18.0.0. This method was used to unmount a React component from the DOM.

In React 19, you should migrate to using the root.unmount() method, which is more aligned with the updated API for creating and hydrating roots.

// Before
unmountComponentAtNode(document.getElementById('root'));

// Now
root.unmount();

5. Removal of hydrate API

ReactDOM.hydrate was deprecated in React 18 and will be completely removed in React 19.

The new method of the React DOM client API, hydrateRoot, replaces ReactDOM.hydrate, providing a more efficient and modern way to hydrate server-rendered React applications.

In a WordPress context, you might use server-side rendering (SSR) to deliver initial HTML content for faster page loads. To hydrate this content into an interactive React application, you would previously use ReactDOM.hydrate:

import { hydrate } from 'react-dom';
import App from './App.js';

hydrate(
  <App />,
  document.getElementById('root')
);

With React 19, you should use hydrateRoot from react-dom/client for hydration:

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(
  document.getElementById('root'),
  <App />
);

Removed deprecated TypeScript types

WordPress developers often use TypeScript to add type safety and improve code quality in React components. With React 19, several deprecated TypeScript types have been removed or relocated to more relevant packages.

Understanding these changes is crucial for ensuring your codebase remains robust and compatible with the latest React version.

To assist with the transition, the React team has provided a tool called types-react-codemod, which can automatically update your codebase to handle these changes.

To use it, run the following codemod command, which includes several transforms to update deprecated types.

npx types-react-codemod@latest preset-19 ./path-to-app

The tool also offers an interactive mode where you can pick specific transforms to apply:

? Pick transforms to apply (Press  to select,  to toggle all,  to invert selection, and  to proceed)
❯◯ context-any
◉ deprecated-react-type
◉ deprecated-sfc-element
◉ deprecated-sfc
◉ deprecated-stateless-component
◯ implicit-children
◯ useCallback-implicit-any

Let’s take a look at some key changes with examples.

1. Ref cleanup required

With React 19, ref cleanup functions improve type safety by enforcing explicit returns in ref callbacks. Implicit returns can cause TypeScript to misinterpret the return value.

// Before
 (instance = current)} />

// Now
 { instance = current }} />

2. useRef requires an argument

Previously, useRef could be called without arguments, leading to potential type issues. In React 19, useRef requires an argument to ensure that refs are always mutable.

// Before — @ts-expect-error: Expected 1 argument but saw none
useRef();

// Now — correct usage with an argument
useRef(undefined);

3. Changes to the ReactElement TypeScript Type

The default type for ReactElement props has changed from any to unknown, improving type safety by requiring explicit handling of unknown types.

// Previously, this was 'any'
type Example = ReactElement["props"];

// Now, this is 'unknown'
type Example = ReactElement["props"];

If your code relied on any, you must update it to handle unknown explicitly or cast it to any.

Summary

As WordPress developers, staying updated with the latest React advancements is crucial. This guide ensures you understand the various changes coming to React so you can apply them to your WordPress projects.

One last piece of information: With React 19, the new JSX transform will be required. The good news is that it already comes with WordPress 6.6. If the new transform isn’t enabled, you’ll see this warning:

Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform

All you have to do is stop using React imports for JSX transformations, as they are no longer necessary.

Did we miss anything? Please share with us in the comments section!

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 300 technical articles majorly around JavaScript and it's frameworks.