Reintroducing React: Every React Update Since v16 [Full Handbook]

React has come a long way since its initial public release in 2013. What started as a simple JavaScript library for building user interfaces has evolved into a rich ecosystem with a highly expressive API, robust performance, and a passionate community.

Numerous significant improvements have shaped the React landscape over the years, but the changes since React 16 in 2017 have redefined how we build modern React applications. Hooks, concurrent mode, and a dramatically improved context API highlight some of the major developments.

In this comprehensive handbook, we‘ll explore every key React update from version 16 through the latest stable release. Whether you‘re new to React or a seasoned veteran, understanding how to leverage these enhancements is essential to writing clean, performant React code in 2023 and beyond.

Table of Contents

  1. New Component Lifecycle Methods
  2. Context API Redesign
  3. Embracing Hooks
  4. Lazy Loading Components
  5. Concurrent Mode Preview
  6. Dropping Legacy Baggage
  7. Profiler API
  8. Looking Ahead

New Component Lifecycle Methods

React 16.3 introduced a few changes to the component lifecycle to better support async rendering. While the core lifecycle remained intact, several methods were added and others deprecated.

getDerivedStateFromProps

One of the more notable additions was a new static method called getDerivedStateFromProps:

class MyComponent extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    // return updated state based on props
  }
}

This method allows a component to update its internal state based on a change in props. It‘s meant to replace the legacy componentWillReceiveProps lifecycle which was often misused in conjunction with setState. By making getDerivedStateFromProps static, it has no access to the component instance, encouraging developers to keep state updates based solely on props and state.

getSnapshotBeforeUpdate

Another new lifecycle method is getSnapshotBeforeUpdate. It‘s called right before mutations are made to the DOM:

class MyComponent extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // return snapshot value or null  
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // use snapshot here
  }
}

This method allows the component to capture information from the DOM before it‘s potentially mutated. The snapshot value it returns is passed as a third parameter to componentDidUpdate. A common use case is capturing the scroll position before the DOM updates.

Together getDerivedStateFromProps and getSnapshotBeforeUpdate enable safer interactions with the DOM and help maintain internal component state. Mastering these lifecycle methods is key to building robust class components.

Context API Redesign

React 16.3 also shipped a new context API to replace the previous experimental and undocumented version. The new API allows for simpler consumption of context values and more predictable behavior.

Creating and Providing Context

To create context, use React‘s createContext method:

const ThemeContext = React.createContext(‘light‘);

This creates both a context Provider and Consumer:

<ThemeContext.Provider value="dark">
  <MyComponent />
</ThemeContext.Provider>

The Provider allows any descendant component to consume the context value. Consumers can then access that value like so:

<ThemeContext.Consumer>
  {value => <div>The theme is {value}</div>}
</ThemeContext.Consumer>

Consumers expect a function as a child which receives the current context value and returns a React node. This pattern, known as render props, is a powerful way to share data across a component tree.

Class.contextType

The other way to consume context is via the contextType class property:

class MyComponent extends React.Component {
  static contextType = ThemeContext;
  render() {
    let value = this.context;
    /* render based on value */
  }
}

Using contextType lets you consume the nearest current value of that context type with this.context. This can only be used for a single context type per class.

The new context API is more efficient and ergonomic than the legacy version. Understanding how to wield it effectively is a critical skill for React developers.

Embracing Hooks

Arguably the biggest change to React since its inception, hooks were added in React 16.8. Hooks provide a mechanism to add state and side-effect logic to function components, making them as powerful as classes.

Built-in Hooks

React ships with several built-in hooks, the most fundamental being useState and useEffect:

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {    
    document.title = `Count: ${count}`;
  });

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

useState allows function components to manage internal state, similar to this.state in classes. useEffect lets you perform side effects like data fetching, subscriptions, or DOM mutations.

Other built-in hooks include:

  • useContext to consume context values
  • useRef to persist mutable values between renders
  • useCallback and useMemo to optimize expensive functions

Custom Hooks

In addition to the built-in hooks, React lets you create custom hooks to share stateful logic between components. Custom hooks are simply JavaScript functions that can call other hooks:

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

function MyComponent() {
  useDocumentTitle(‘My Page Title‘);
  return <div>Hello World</div>;
}

By extracting hooks to custom functions, you can create a rich library of reusable behavior. This unlocks patterns for data fetching, animations, form handling, and more.

Hooks represent a paradigm shift for React development. Mastering the various hooks and patterns they enable is paramount to writing modern, maintainable React code.

Lazy Loading Components

As applications grow, it‘s important to keep bundle sizes in check. React 16.6 introduced a new way to lazily load components using the React.lazy and Suspense APIs:

const MyComponent = React.lazy(() => import(‘./MyComponent‘));

function MyApp() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent />
    </Suspense>
  );
}

React.lazy takes a function that returns a promise which resolves to a module with a default export containing a React component. The lazily-loaded component should then be rendered inside a Suspense component which allows you to show a fallback while waiting for the lazy component to load.

Code splitting with React.lazy improves application performance by reducing the amount of code needed during the initial load. Learning how to structure your components for lazy loading is key to building snappy, responsive apps.

Concurrent Mode Preview

React‘s much anticipated concurrent mode, which allows rendering to be interruptible, was previewed in the experimental release channel with React 16.6. While not yet stable, concurrent mode offers a glimpse into the future of the React runtime.

Some of the key features concurrent mode enables include:

  • Time slicing to allow rendering work to be split into chunks
  • Suspense for data fetching to declaratively await asynchronous data
  • Selective hydration to progressively upgrade server-rendered HTML

Concurrent mode is a foundational update to React internals and will eventually power techniques like rendering transitions, deferred updates, and new data fetching patterns.

Although still experimental, React developers would be wise to start familiarizing themselves with concurrent mode and its associated APIs. Many of the long-term React best practices will be shaped by this new operating environment.

Dropping Legacy Baggage

To keep the React ecosystem lean and current, the core team occasionally removes support for legacy features and APIs. React 17 eliminated a few key vestiges:

  • No event pooling – The event object in event handlers will no longer be reused across invocations
  • No effect cleanup timing change – Effects are always executed asynchronously, even if the component is unmounting
  • Removal of old context API – The legacy context API is no longer supported
  • Removal of legacy factories – React.createFactory and React.createElement factories have been removed

Staying current with legacy code elimination helps keep your React codebase maintainable and aligned with the latest best practices. Be sure to follow the React blog and release notes for guidance on upcoming deprecations.

Profiler API

To help developers debug performance issues, React 16.5 introduced a new Profiler API. The Profiler measures how often a component renders and what the cost of each render is:

render(
  <Profiler id="application" onRender={onRenderCallback}>
    <App />
  </Profiler>
);

function onRenderCallback(
  id, // the "id" prop of the Profiler tree that has just committed
  phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
  actualDuration, // time spent rendering the committed update
  baseDuration, // estimated time to render the entire subtree without memoization
  startTime, // when React began rendering this update
  commitTime, // when React committed this update
  interactions // the Set of interactions belonging to this update
) {
  // Log or store render timings...
}

The Profiler will call the onRender callback with timing information every time a component within the profiled tree mounts, updates, or unmounts. This is useful for tracking down wasted renders and identifying performance bottlenecks.

To get the most out of the Profiler, you should have a solid grasp on React‘s reconciliation process and how to optimize rendering with techniques like memoization and code splitting. The Profiler API is a powerful tool, but it‘s most effective in the hands of a performance-savvy developer.

Looking Ahead

React‘s trajectory since version 16 has focused on new ways to compose UI, manage application state, and optimize rendering. These themes will likely continue through future releases as concurrent mode stabilizes and hooks reach full maturity.

Some exciting new possibilities on the horizon include:

  • Server components to integrate backend logic more seamlessly with React‘s component model
  • Offscreen rendering to efficiently render content outside the viewport
  • Transition tracing to better visualize and measure React‘s rendering pipeline

Of course, the React team will also keep a keen eye on the evolution of web standards and surrounding technologies. As the JavaScript ecosystem matures, React will undoubtedly adapt to provide an effective, modern development experience.

No matter what the future holds, understanding the techniques and best practices outlined in this handbook will be essential knowledge for React developers. By staying current with React‘s latest patterns and APIs, you‘ll be ready to build exceptional user experiences for years to come.


This handbook has covered all the major React updates and features released since version 16. From the more ergonomic context API to the paradigm-shifting addition of hooks, recent React versions have reshaped the development landscape.

Internalizing this knowledge is key to building modern, performant, and scalable React applications. While there will always be something new to learn, mastering the concepts behind lifecycle methods, hooks, concurrent mode, and more will keep you on the cutting-edge of React development.

Now that you‘re up to speed on the latest React has to offer, go build something amazing!

Similar Posts