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
- New Component Lifecycle Methods
- Context API Redesign
- Embracing Hooks
- Lazy Loading Components
- Concurrent Mode Preview
- Dropping Legacy Baggage
- Profiler API
- 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 valuesuseRef
to persist mutable values between rendersuseCallback
anduseMemo
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
andReact.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!