React 19: From server functions to optimistic updates
The release paves the way for full-stack applications, but also brings many innovations for the development of single-page applications.
- Nils Hartmann
As the saying goes, some JavaScript frameworks have a shorter shelf life than a liter of fresh milk. This does not apply to React for several reasons. Firstly, the library celebrated its tenth birthday last year, making it an "old hand" in the web world. React is also practising restraint when it comes to release frequency: the last major release appeared in June 2022. Since then, there have only been a few bugfix releases.
This only changed on December 5, 2024, when the final version of React 19 was published after a release candidate phase lasting several months. This not only contains new functions and hooks, but also completely new concepts that should pave the way for full-stack development with React.
Support for metadata
The new functions for working with the metadata of a website fall into the category of small but useful innovations in React 19. Metadata are DOM elements such as title, meta or link in the head area of the DOM. They are therefore not normally part of the React component hierarchy and cannot be easily used with React on-board tools. Instead, libraries are used for this in many projects.
With React 19, these elements are now also available in JSX and can therefore be used in every component. React ensures that the elements move to the correct position in the head element at runtime. If there are several such elements in a component hierarchy, the element that is furthest down in the component hierarchy "wins".
The following listing illustrates this behavior using the title element. The app component sets the title of the browser window to "Blog Application". If the BlogPost sub-component is also rendered, the sub-component overwrites the title so that it now corresponds to the title of the displayed blog post.
function App({postId}) {
return <main>
<title>Blog Application</title>
{!!postId && <BlogPost postId={postId} />}
</main>
}
function BlogPost({postId}) {
const post = useBlogQuery(postId);
return <article>
<title>{post.title}</title>
{/* ... */}
</article>
}
In addition to title and other metadata, script elements and external stylesheet files can now also be rendered directly in a component. Here too, React ensures that the corresponding script or link elements are placed at the beginning of the DOM and ensures that a component is only rendered once the browser has loaded the corresponding resources. This makes it much easier to work with dynamically added scripts (for example from external providers such as the Google Maps API) or stylesheets. The metadata features are complemented by the option to preload resources. For this purpose, developers can specify resources such as stylesheets or fonts in the head element of the DOM, which the browser downloads in advance in the background so that they are already available locally if required. With React 19, it is possible to specify such resources in the code. React then takes care of generating the corresponding DOM elements.
A particular challenge in React applications is optimizing or saving render cycles. React always re-renders a component as soon as its state has changed, including its subcomponents. There is no finer granular tracking of changes in React, such as with signals in Angular. This is not necessarily a problem, as rendering is usually very fast, partly because the changes only take place in the virtual DOM and React reduces potentially expensive changes to the DOM to an absolute minimum. But with deep component hierarchies or complex components, it can lead to performance problems. This is why there is the memo function, which can cache a finished component, as well as the useMemo and useCallback hooks to cache data or functions within a component.
However, using these three functions is prone to errors because the mechanism that React uses to check whether the cached objects are still valid or need to be recreated works with reference comparisons. As soon as a reference is (accidentally) recreated too often, the cache is de facto switched off. In addition, useMemo and useCallback calls in component functions quickly lead to confusing code.
The new React compiler aims to reduce these problems. It should enable React developers to dispense with memo, useMemo and useCallback in many places. The compiler analyzes the written code and then generates optimized code as output, which behaves similarly to today's code that works with handwritten memo, useMemo and useCallback calls and thus ensures fine-grained updates.
The React Compiler is a standalone project and will be released independently of the React 19 release.
Simplification of references and context
Small simplifications have also been made to existing APIs. For example, the ref attribute has become a normal property that can be declared and used – like all other properties – in the properties of a component. The somewhat cumbersome forwardRef function is therefore superfluous and has been marked as deprecated by the React team. Creating and offering a context has also been simplified somewhat. The createContext function now returns the provider component directly. The division into consumer and provider components is no longer necessary, as the consumer component has not been necessary for some time due to the introduction of the useContext hook.
Regardless of how the context is created, the use function provides a new option for accessing – or a Promise – within a component. In contrast to useContext, the use function is not a hook (therefore it is also described under API and not under Hooks in the documentation) and is therefore not subject to the "Rules of Hook". Accordingly, a hook function may not be called within a if statement, for example. However, the use function can be used like any other normal function.
The next listing shows this behavior. The CounterContext in the component is only necessary if the checkbox is clicked, otherwise the component will not use its data. With useContext, however, the context should always be queried, regardless of the state of the checkbox. With use, the component only queries the context if the checkbox is clicked accordingly. This not only affects the code, but also the runtime: If the checkbox is not checked and the context is therefore not queried with use, React does not re-render the component if the value of the context changes. When using useContext, the component is also rendered again if the value of the context is not actually necessary (unchecked checkbox).
export const CounterContext = createContext(null);
function CounterContextProvider{children}) {
const value = { /* ... */ }
// bisher:
return <CounterContext.Provider value={value}>{children}</CounterContext.Provider>
// React 19:
return <CounterContext value={value}>{children}</CounterContext>
}
function CounterDisplay() {
const [selected, setSelected] = useState(false);
const counter = selected ? use(CounterContext) : null;
return (
<section>
<label>
Wert anzeigen
<input
type="checkbox"
checked={selected}
onChange={e => setSelected(e.target.checked)}
/>
</label>
{counter && <p>Aktueller Wert: {counter.value}</p>}
</section>
);
}
Videos by heise
Smooth transitions: Transitions
There are a number of new possibilities when working with asynchronous actions, such as reading and writing server-side data. React 18 already introduced transitions. This allows updates to the interface to be prioritized. Normally, changing a state causes React to immediately re-render the component and the updated user interface (UI) is immediately visible in the browser.
With a transition, developers can specify a callback function. This can be used to change the state of the component. However, the component is now rendered in the background. The existing UI does not change at first. The UI is only updated after the background rendering has been completed.
This behavior is particularly useful for updates that take a long time and would otherwise block the UI. With a transition, recalculations take place in the background and the "old" UI remains in place until the background rendering was successful. To enable a component to display an indication of the current update, for example, a transition provides information on whether it is currently being executed.
The listing shows this behavior in the React 18 variant using a very simple example:
function Counter() {
const [count, setCount] = useState(0);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1);
});
};
return <section>
<button onClick={handleClick}>Hochzählen</button>
{isPending && <p>Darstellung wird aktualisiert.</p>}
<CounterView count={count} />
</section>
}
Here, CounterView is supposed to be a complex subcomponent and take some time to render after changing the count state. Due to the transition in which the state change takes place, rendering takes place in the background. React now immediately renders the Counter component, but initially leaves the count state in its current value. However, the useTransition call now returns true for isPending. The component can therefore immediately display the information "Display is being updated". The component is now re-rendered in the background. There, count is set to the new value and isPending is false. The component can therefore render the future display. As soon as rendering is complete, React replaces the old display with the new one. If an error occurs in the background during rendering, React discards the new representation without replacement.
A new feature in React 19 is that a function used as a transition callback can be asynchronous. Such functions are referred to as actions. This allows server calls to be outsourced to a transition, for example. The previous display is then retained until the asynchronous server call is completed. As in the previous example, however, React provides information on whether the transition is running, so that a wait message can appear, for example.
The listing shows a transition with asynchronous action using a component that represents a button with a like counter:
async function registerLikeOnServer() {
// Server-Call z. B. mit fetch ausfĂĽhren
// neue Likes vom Server zurĂĽckliefern...
}
function LikeButton({initialLikes}) {
const [likes, setLikes] = useState(initialLikes);
const [isPending, startTransition] = useTransition();
const handleClick = () => {
startTransition(async () => {
const newLikes = await registerLikeOnServer();
setLikes(newLikes);
});
};
return <button onClick={handleClick}
disable={isPending}>{likes} Likes</button>
}
A click on the button triggers the execution of a server call, which increases the number of likes on the server and returns the new number (the code for this is React-independent and omitted from the listing for the sake of simplicity). After clicking on the button, React immediately renders the component with the old state, but returns true for isPending. This allows the button to display the disabled status while it is being saved. However, the server call is already running in the background. As soon as this is completed, the status is reset and the component is then rendered and displayed in the new status.
(Image:Â WD Ashari/Shutterstock.com)
enterJS 2025 will take place on May 7 and 8 in Mannheim. The conference offers a comprehensive look at the JavaScript-supported enterprise world. The focus is not only on the programming languages JavaScript and TypeScript themselves, but also on frameworks and tools, accessibility, practical reports, UI/UX and security.
The program is expected to go live in mid-January. Until then, discounted blind-bird tickets can be purchased in the online store.