React is one of the most widely used libraries for building modern web applications. But what makes it so popular? Why is it chosen by so many developers and companies, including mine? Well, this is for several reasons.
React is a highly flexible tool that can be used to create a variety of web applications, from simple single-page applications to complex and scalable systems. Its component-based architecture promotes code reusability and maintainability. It is easy to learn and use. By using JSX, developers can write code with declarative HTML syntax directly within JavaScript, which makes it more readable and easier to write. React was designed to provide high performance. It offers optimisation techniques, such as virtual DOM, code splitting, or server-side rendering, which help make page loading and updating really fast.
React also benefits from strong community support, which means plenty of resources, libraries, and tools are available to help developers build applications more efficiently. What’s more, it is constantly developing and improving. In fact, a new React 19 is available on npm. This version introduces features aimed at simplifying web development and boosting performance. Let’s briefly review some of them.
Most common web technologies used by professional developers
Actions
A significant part of web applications involves data mutation and response handling. Previously, in the standard approach during the data mutation, we had to handle pending states, errors, and optimistic updates manually. Now React 19 provides us with support for async functions in transitions to handle these things automatically. Functions that use async transitions are called Actions.
Here is an example showing how this feature can handle the pending state for us.
import { useState, useTransition } from "react";
const updatePost = (title) =>
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PATCH",
body: JSON.stringify({
title,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
}).then((res) => res.json());
export function UpdatePost() {
const [title, setTitle] = useState("");
const [isPending, startTransition] = useTransition();
const onSubmit = () => {
startTransition(async () => {
await updatePost(title);
setTitle("");
});
};
return (
<div>
<input
value={title}
onChange={(event) => setTitle(event.target.value)}
disabled={isPending}
/>
<button onClick={onSubmit} disabled={isPending}>
Save
</button>
</div>
);
}
The pending state begins at the start of a request and resets automatically when the final state update is accomplished.
Actions simplify working with forms. They allow passing a function to DOM elements such as <form action={actionFn} />
and call it when a form is submitted. In addition, React introduces two new hooks: useFormStatus
and useActionState
, which makes working with forms even simpler by providing more intuitive ways to track form status and action outcomes.
useFormStatus hook
The useFormStatus
provides information about the parent <form>
submission status, such as whether it is pending. It also delivers details about data being transferred, the method by which the data is being submitted, and the reference to the function passed to the action prop.
In the example below, pending information is used to disable <button>
while the form is being submitted.
import { useFormStatus } from "react-dom";
/* Nested component placed in <form> */
export function NestedButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending} />;
}
useActionState hook
The useActionState
will update the state according to the result of an Action. Basically, it returns the last results of the function passed to the hook, called an Action, the wrapped Action that you can pass as the prop to your form or button component, and the pending state of the Action. The following code shows an example of using this hook.
import { useActionState } from "react";
const updateTitle = (title) =>
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PATCH",
body: JSON.stringify({
title,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
});
export function UpdateTitle() {
const [message, submitAction, isPending] = useActionState (
async (previousState, formData) => {
const response = await updateTitle(formData.get("title"));
if (response.ok) {
return "Title successfully changed";
}
return "Error";
},
null,
);
return (
<form action={submitAction}>
<input type="text" name="title" />
<button type="submit" disabled={isPending}>Save</button>
{message && <p>{message}</p>}
</form>
);
}
At the beginning, the message takes an initial state passed to the hook if there is any. After the form is submitted (the action is invoked), it will correspond to the value returned by the Action.
useOptimistic hook
useOptimistic
hook can significantly improve user experience, making an application feel more responsive. With this feature, you can make temporary updates to the UI when the async function, like a network request, is underway. For example, when a user submits a form, the application is updated immediately with the expected outcome instead of waiting for the server’s response.
import { useOptimistic, useState } from "react";
const updatePost = (title) =>
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PATCH",
body: JSON.stringify({
title,
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
},
}).then((res) => res.json());
export function UpdatePost({ currentTitle, onUpdate }) {
const [optimisticTitle, setOptimisticTitle] = useOptimistic(currentTitle);
const disabled = currentTitle !== optimisticTitle;
const submitAction = async (formData) => {
const newTitle = formData.get('title');
setOptimisticTitle(newTitle);
const result = await updatePost(newTitle);
onUpdate(result.title);
};
return (
<form action={submitAction}>
<p>Current title: {optimisticTitle}</p>
<input type="text" name="title" disabled={disabled} />
<button type="submit" disabled={disabled}>
Save
</button>
</form>
);
}
export default function App() {
const [post, setPost] = useState({
id: 1,
title: "New Post!",
userId: 1,
});
const onUpdate = (title) => setPost({ ...post, title });
return <UpdatePost currentTitle={post.title} onUpdate={onUpdate} />;
}
use API
Use
is a new API that lets you read the value of a resource such as a promise or context. Like React hooks the function that calls use
must be a component or hook but there are also some differences. Use
is more versatile (it fulfils more than one task) and can be used in loops and conditional statements like for
or if
.
When a context is passed to use
, it can replace useContext
. It works similarly but is less restricted regarding the location from which it can be called.
export function exampleFunction (show) {
if(show) {
const theme = use(ThemeContext);
return <hr className={theme} />
}
return false;
}
You can also use it as an alternative to the useEffect
hook for things like data fetching. Together with Suspense
and ErrorBoundary
, it can create more readable and cleaner code. Below, you can find examples of fetching users’ data. One with a traditional approach using useEffect
, the second containing a use
API. Notice how much more readable the new approach is.
/* Traditional approach with useEffect */
import { useState, useEffect } from "react";
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
if (res.ok) {
return res.json();
} else {
throw new Error('Request failed!');
}
};
const UsersItems = ({ users }) => {
return (
<div>
{users.map((users) => (
<div key={users.id}>{users.name}</div>
))}
</div>
);
};
const Users = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(undefined);
useEffect(() => {
setLoading(true);
fetchUsers()
.then((data) => setUsers(data))
.catch((err) => setError(err))
.finally(() => setLoading(false));
}, []);
if (loading) {
return <div>Loading...</div>
}
if (error) {
return <div>Error</div>
}
return <UsersItems users={users} />;
};
export default Users;
/* New approach with use API */
import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
return res.json();
};
const UsersItems = () => {
const users = use(fetchUsers());
return (
<div>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
};
const Users = () => {
return (
<ErrorBoundary fallback={<div>Error</div>}>
<Suspense fallback={<div>Loading...</div>}>
<UsersItems />
</Suspense>
</ErrorBoundary>
);
};
export default Users;
Server Components
React Server Components (RSC) is a feature that enables components to be rendered ahead of time in a different environment than the client application. Server components can either run at build time to read data from the filesystem or fetch static content, or be executed on a web server during a request for a page. In the second case, there is no need to build an API or add client-side fetching logic to access data from the server. It’s important to note that Server Components are not sent to the browser, so they cannot use interactive APIs like useState
or HTML events. To make Server Components interactive, you can combine them with Client Components.
import ClientComponent from "./ClientComponent";
export default async function ServerComponent() {
return (
<main>
<p>This runs on the server</p>
<ClientComponent />
</main>
);
}
"use client";
import { useState } from "react";
export default function ClientComponent({ children }) {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "Hide" : "Show"}
</button>
{isVisible && children}
</div>
);
}
Serve Components can significantly improve performance by allowing the server to handle more of the rendering logic, leading to faster load times and a better user experience. What’s more React Server Components revolutionise how we design React applications by clearly distinguishing between client-side and server-side responsibilities.
Server Actions
Directives are an integral part of the Server Component architecture. The Server Components are responsible for fetching data and static rendering, while the Client Components handle interactive application elements. The directive use server
allows us to define server-side functions that can be called from client-side code. When a function is called, React sends a request to the server to execute it and return the result. Server Actions can be created in Server Components and passed as props to Client Components or imported and used in Client Components. They can also be composed with Actions on the client side.
Document Metadata
React 19 introduces support for metadata like titles or meta tags. You can place the tags anywhere within your component tree, and they will work for both the client and server components. This is a built-in solution that previously could be achieved by external libraries like React Helmet.
export const Home = () => {
return (
<div>
<title>Home</title>
<meta name="description" content="Company Home Page" />
</div>
);
};
Asset loading
In React 19, styles are managed more effectively with built-in stylesheet support in Concurrent Rendering on the client and Streaming Rendering on the server. React allows developers to determine the precedence of stylesheets and, based on this, manages the order in which they are inserted. For Server Side Rendering, React puts stylesheets in the <head>
to prevent the browser from painting until they are fully loaded. Regarding Client Side Rendering, React waits for stylesheets to load before committing the render. Additionally, if a component is used in multiple places, React will include the stylesheet only once.
Also, new Resource Loading APIs like preload
and preinit
have been added to provide greater control over when a resource should load and initialize. These APIs can be used to preload resources such as scripts, stylesheets, and fonts as soon as they are needed, for example, before navigating to another page.
Improvements
The latest version includes some minor improvements that can enhance our daily work. For instance, the forwardRef
function is no longer necessary. Now, you can pass a ref like any other prop, eliminating the need to wrap your component in the forwardRef
function. Additionally, there is no need to use <Context.Provider>
, you can now render just <Context>
. React also adds support for an initial value in the useDeferredValue
hook and a cleanup function in the ref callback that will be called when the component unmounts. The cleanup function works for DOM refs, refs to class components, and useImperativeHandle
hook.
Conclusion
React 19 comes with many new features, including Actions, Server Components, and new hooks and APIs that will make developers’ work simpler and more efficient. Additionally, changes will improve app performance, resulting in better user experiences and faster loading times.
The stable version of React 19 is now available on npm. However, before transitioning to React 19, we can first upgrade to react@18.3. This version contains additional warnings about deprecated APIs and changes required for React 19, as the new version involves some breaking changes.
You can follow the React team on the following places to stay updated: