Table of contents
Regarding JS libraries and frameworks, React stands out due to the large community of developers that have adopted it as their preferred UI library and its large ecosystem of third-party packages that can make development easier and faster.
As always, React tends to release newer versions with new and improved features in an attempt to fix previous bugs. On April 25, 2024, React 19 was released, and it served as another stepping stone in the development of the React ecosystem, providing a variety of new features and updates to help make development less tedious. In this article, we will go over the new features that React 19 has to offer React developers.
Pre-requisites:
knowledge of ReactJS, Tailwind CSS
knowledge of HTML, CSS, and JavaScript
Key Features of React 19
Let’s delve straight into the topic. The most important features of React 19 to be discussed extensively are as follows:
1. React Compiler
React 19 offers a compiler similar to other frameworks, such as Angular and Svelte. This compiler basically converts React code into regular JS code. This will help to analyze React code at build time and automatically optimize it for better performance, especially when dealing with updates and re-renders. Most of the new features in React 19 are directly related to the addition of the compiler.
The compiler replaces the need for us to write code using hooks like useMemo
, and useCallback
to optimize components. These hooks are currently used to manually control how React re-renders parts of your UI.
Let’s take a look at a code example where manual memoization is needed before looking at one involving a compiler:
import { useState, useCallback, useMemo } from 'react'
const Increment = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((c) => c + 1), []);
const double = useMemo(() => count * 2, [count]);
return(
<>
<div>count: {count}</div>
<div>doubleCount: {double}</div>
<button onClick={increment}>Increment</button>
</>
)
}
export default Increment;
In this code, the Increment
component is a simple counter that displays the current count and its double. The count
state is used to store the current value of the counter. The increment
function is used to increase the count by 1. If we use useCallback
, it ensures that the function is created only once, improving performance. The double
value is calculated based on the count and is memoized using useMemo
to avoid unnecessary recalculations. The increment component renders the count
, doubleCount
, and a button to increment the count.
Now, let’s see how React Compiler removes the use of usecallback
and useMemo
hooks:
import { useState } from 'react'
const Increment = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
const double = count * 2;
return(
<>
<div>count: {count}</div>
<div>doubleCount: {double}</div>
<button onClick={increment}>Increment</button>
</>
)
}
export default Increment;
Note that we only imported the useState
hook. In the Increment
component, useState(0)
initializes a state variable count to 0. SetCount
changes the count state. The increment
function modifies the count state by adding 1 to the current value of setCount
. The double
variable returns a calculated value based on the current count state.
2. Server components
Server components are a new way to render React components ahead of time on the server. By default, React components render on the client side in the user's browser. This is a feature that already exists in next.js, which is a framework built on React, where all components are server components. These server components will render on the server and generate HTML that gets sent to the client. The code will not be included in the JavaScript bundle sent to the browser, resulting in faster initial load times.
To allow React code to run on the server, add a directive at the top of our component file. We must write 'use server' on the component's first line to convert it into a server component. Server components provide the following advantages:
Improved SEO: Search engines may simply scan and index the content of server-rendered components, resulting in improved Search Engine Optimization (SEO). This is because the original HTML delivered to the client already has the content, making it easier for search engines to understand.
Faster initial load times: Since server components are rendered on the server, the browser receives the first HTML content much more quickly. This results in a loading time perceived as fast for users, particularly on slower connections.
Enhanced Performance: By shifting rendering work to the server, server components can boost our application's overall performance. This is because the client-side JavaScript bundle is smaller, resulting in speedier download and execution times.
Let’s take a look at how to apply the ‘use server’
in a component:
// serverComponent.js
'use server';
async function Directive() {
const response = await fetch('https://api.quotable.io/random');
const data = await response.json();
const quote = data.content;
const author = data.author;
return (
<div>
<p>{quote}</p>
<p>- {author}</p>
</div>
);
}
export default Directive;
The line 'use server'
turns the component into a server component. Directive
is defined as an asynchronous functional component that uses async
. This enables the component to use await
to wait for network requests.
const response = await fetch('
https://api.quotable.io/random
')
gets information from the API endpoint for a random quote. await
stops the function's execution until the promise returned by fetch
is resolved, at which point the response is obtained. const data = await response.json();
parses the response body into JSON
. const quote = data.content
, and const author =
data.author
extracts the quote and author information, respectively, from the parsed JSON
object.
3. Actions
The Actions
feature was created to make form handling easier. They are basically functions that are called when a form is being submitted. It separates form submission logic from the component itself, and this makes the code cleaner. Instead of using the onSubmit
event, we can use the action
attribute. Just like the onSubmit
event, this takes a function that allows us access to the submitted form data for easy form handling.
Actions can be used on both the client-side and server-side, and this means we can execute both synchronous and asynchronous operations with actions.
Let’s take a look at an example where the action
attribute is being used on the server-side:
'use server'
const formAction = (formData) => {
const newPost = {
Title: formData.get('Title'),
Body: formData.get('Body')
}
console.log(newPost)
}
const Form = () => {
return <form action={formAction}>
<div>
<label>Title</label>
<input type="text" name='Title'/>
</div>
<div>
<label>Body</label>
<input type="text" name="Body" />
</div>
<button type='submit'>Submit</button>
</form>
}
export default Form;
The line 'use server'
is used to mark the Form
component as a server component and it returns a form element. The action
attribute of the form is set to the formAction
function. The form contains two input fields, Title
and Body
. A submit button is also included.
formAction
refers to the action
function replacing the onSubmit
event, and it takes formData
as an argument. We extract the Title
and Body
values from the form data and create a newPost
object. We log out newPost
to the console.
4. use() API
The use() API
is a new feature in React 19, and it basically allows us to read and asynchronously load a promise or a context. It is quite versatile and can be used for fetching data instead of using the useEffect
hook. It is going to replace the useContext
hook because we can simply pass in a context as a parameter to the use() API
(use(context)).
Let’s take a look at how the use() API
hook will replace useEffect
when it comes to fetching data:
Fetching data with useEffect Hook
import { useState, useEffect } from 'react';
const UserItems = ({ users }) => {
return (
<ul>
{users.map((user) => (
<div key={user.id}>
<h2>{user.name}</h2>
<p>{user.phone}</p>
</div>
))}
</ul>
);
};
const Users = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUsers = async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
setUsers(data);
setLoading(false);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) {
return <div>Loading...</div>;
}
return <UserItems users={users} />;
};
export default Users;
In this code, we use useState
to create two state variables,users
and loading
within the Users
component. Users
is an array to store user data, initially empty ([]). Loading
is a boolean indicating whether data is being fetched (initially true). In this Users component, we also use the useEffect
hook with an empty dependency array ([]) to run the effect code only once after the component mounts.
Inside the useEffect
hook, an async function fetchUsers
is defined to fetch data from an API. The function fetches data from https://jsonplaceholder.typicode.com/users using fetch
and parses the response as JSON
. On successful fetch, it updates the users
state with the fetched data and sets loading
to false
. In the case of an error, it logs the error to the console. Finally, it sets loading
to false
to indicate the loading state regardless of success or failure.
The Users
component conditionally renders content based on the loading
state. If loading
is true, it displays a "Loading..." message. If loading
is false (data is fetched), it renders the UserItems
component, passing the fetched users
data as a prop.
The UserItems
component takes a users
prop containing an array of user objects. We map over the users
array and render a list item for each user. Each list item displays the user's name in a <h2>
tag and their phone number in a <p>
tag. The key
prop on the div
is set to the user's id
to improve performance.
Fetching Data With Use() API
import { use, Suspense } from 'react';
const fetchUsers = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
return res.json();
};
const UserItems = () => {
const users = use(fetchUsers());
return (
<ul>
{users.map((user) => (
<div key={user.id}>
<h2>{user.name}</h2>
<p>{user.phone}</p>
</div>
))}
</ul>
);
};
const Users = () => {
return (
<Suspense
fallback={
<h1>Loading...</h1>
}
>
<PostItems />
</Suspense>
);
};
export default Users;
Let’s take a look at how the use() API
will replace useContext
hook:
import React, { createContext, useState, use } from 'react';
// Create a context object
const ThemeContext = createContext();
// Create a provider component
const ThemeProvider = ({ children }) => {
// State to hold the current theme
const [theme, setTheme] = useState('light');
// Function to toggle theme
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
// Provide the theme and toggleTheme function to the children
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const ThemedCard = () => {
// Access the theme context using the use() hook
const { theme, toggleTheme } = use(ThemeContext);
return (
<div
className={`max-w-md mx-auto shadow-md rounded-lg p-6 ${
theme === 'light' ? 'bg-white' : 'bg-gray-800'
}`}
>
<h1
className={`text-2xl mb-3 ${
theme === 'light' ? 'text-gray-800' : 'text-white'
}`}
>
Themed Card
</h1>
<p className={theme === 'light' ? 'text-gray-800' : 'text-white'}>
This card magically changes color. Don’t ask me how.
</p>
{/* Toggle button */}
<button
onClick={toggleTheme}
className='mt-4 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md focus:outline-none focus:ring-2 focus:ring-blue-600'
>
{theme === 'light' ? 'Switch to Dark Mode' : 'Switch to Light Mode'}
</button>
</div>
);
};
const Theme = () => {
return (
<ThemeProvider>
<ThemedCard />
</ThemeProvider>
);
};
export default Theme;
In this example, we use createContext
to create a new context object named ThemeContext
.
ThemeProvider
is the component that manages the theme state. ThemeProvider
uses the useState
hook to manage the theme
state, and it also provides the theme
and toggleTheme
functions to its children using ThemeContext.Provider
.
We use the ThemedCard
component to access the theme context using useContext(ThemeContext)
and extract theme
and toggleTheme
from the context. It conditionally applies styles based on the theme
value, and there is a button within it for toggling the theme.
We use the Theme
component to wrap ThemedCard
with ThemeProvider
to make the theme
context available to ThemedCard
.
5. The useFormStatus() hook
The useFormStatus
hook is a new React hook that provides information about the status of the last form submission. It's useful for tracking the status of form submissions and delivering feedback to users.
It returns a status
object, which contains information about the form's current state. It also provides us with a pending property to indicate whether the form is currently submitting. The useFormStatus
hook also returns data after a successful form submission or errors encountered during form submission.
Let’s take a look at a use case of the useFormStatus
hook:
import { useFormStatus } from 'react-dom'
const SubmitButton = () => {
const { pending } = useFormStatus();
console.log(pending);
return (
<button type='submit' disabled={pending}>{pending ? 'Submitting...' : 'Submit'}
</button>
);
};
const formAction = async (formData) => {
await new Promise((resolve) => setTimeout(resolve, 2000));
const newPost = {
Title: formData.get('Title'),
Body: formData.get('Body')
}
console.log(newPost)
}
const Form = () => {
return <form action={formAction}>
<div>
<label>Title</label>
<input type="text" name='Title'/>
</div>
<div>
<label>Body</label>
<input type="text" name="Body" />
</div>
<div>
<SubmitButton />
</div>
</form>
}
export default Form;
In this code, we import the useFormStatus
hook from the react-dom
package. const { pending } = useFormStatus();
destructures the pending property from the hook's return value. The pending
property is a boolean indicating whether the form is currently being submitted.
The SubmitButton
component renders a submit button and uses the pending
value to conditionally disable the button and change its text based on the status of our submission.
Inside the Form component, we have two input fields for Title
and Body
. The SubmitButton
component is nested within the form. The action
attribute of the form is set to formAction
.
formAction
takes a new Promise
that resolves after 2 seconds during form submission. It creates a newPost
object from the form data. We only log the newPost
object onto the console, but normally we would send the form data to a server.
6. The useActionState() hook
The useActionState
hook uses action
functions that handle form submission logic and an initial state for the form. This initial state is then updated accordingly, depending on the outcome of a form submission. In a way, it’s similar to the useState
hook, but it only works for actions.
The syntax is as follows:
const [state, formAction] = useActionState(fn, initialState, permalink);
The parameters are as follows:
1. fn
: The fn
function is called after a form is submitted or a button is clicked. When invoked, it receives the form's previous state value as its first argument and the submitted form data.
2. initialState
: The initial value we want the state to be.
3. permalink
: This is negligible. If fn
is an action that will run on the server and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL.
The useActionState
hook returns the following:
1. state
: Before an action is invoked, the current state matches the initialState
we have passed. Once the action is invoked, it will match the value returned by the action.
2. formAction
: It returns a new action that we can pass as action
prop to our form component or formAction
prop to any button component within the form.
Let’s take a look at an example:
import { useFormState} from 'react-dom';
const ActionState = () => {
const submitForm = (prevState, formData) => {
const name = formData.get("username");
if(name === 'john' || "jack"){
return {
success: true,
text: `Welcome ${name}`
}
}
else{
return {
success: false,
text: "Error"
}
}
}
const [ message, formAction ] = useFormState(submitForm, null)
return <form action={formAction}>
<label>Name</label>
<input type="text" name="username" />
<button>Submit</button>
{message && <h1>{message.text}</h1>}
</form>
}
export default ActionState;
In this code, we import the useFormState
hook from the react-dom
package. const [ message, formAction ] = useFormState(submitForm, null)
defines how we apply the useFormState
hook, and it does the following:
It creates a state variable
message
to store the form submission result (success or error).It creates
formAction
to handle the form submission.The
submitForm
function is passed as the first argument touseFormState
to define the form submission logic.The second argument,
null
, is the initial form data. In this example, there is no initial data.
We pass the submitForm
function as the first argument to the useFormState
hook. By default, submitForm
is expected to take prevState
(previous form state) and formData
as parameters. However, we don’t need prevState
in this example. The submitForm
function basically extracts the username from the form data and checks if the username is either "john" or "jack". If the username matches, it returns a success message with the username. Otherwise, it returns an error message.
We used the ActionState
component to create a form with a name input field and a submit button. The action
attribute of the form is set to the formAction
function. Inside the ActionState
component, a success or error message is displayed based on the message
state.
7. The useOptimistic() hook
The useOptimistic
hook is a React hook that allows us to implement optimistic updates in our applications. This means we can update the UI immediately after a user action, even before the server confirms the operation. This creates a more responsive and fluid user experience. When a user submits a form or sends a message, instead of waiting for the server’s response to reflect the changes, the interface is immediately updated with the expected outcome in an optimistic manner. We would see our form data or message along with a pending property such as a “sending…” flag.
The syntax is as follows:
const [ optimisticState, addOptimisticState] = useOptimistic(state, updatefn);
The parameters are as follows:
1. State
: This is the value to be returned initially and anytime no action is pending.
2. updateFn(currentState, optimisticValue)
: This function accepts the current state and the optimistic value passed to addOptimistic
and returns the optimistic state.
The useOptimistic
hook returns the following;
1. optimisticState
: This is the optimistic state. It is equal to the initial state value unless an action is in progress, in which case it is the value returned by updateFn
.
2. addOptimisticState
: This refers to the function to call when we have an optimistic update. It will call the updateFn
with state and optimisticValue
.
Let’s take a look at an example where the useOptimistic
hook is applied:
import { useOptimistic, useState, useRef } from 'react';
const MessageForm = ({ addOptimisticMessage, sendMessage }) => {
// Create a reference to the form
const formRef = useRef();
// This function is called when the form is submitted
const formAction = async (formData) => {
addOptimisticMessage(formData.get('message'));
// Clear the form
formRef.current.reset();
await sendMessage(formData);
};
return (
<form action={formAction} ref={formRef} className='flex items-center mb-5'>
<input
type='text'
name='message'
placeholder='Hello!'
/>
<button
type='submit'
>
Send
</button>
</form>
);
};
const Thread = ({ messages, sendMessage }) => {
// The useOptimistic hook is used to add an optimistic message to the list of messages
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true,
},
]
);
return (
<div>
<MessageForm
addOptimisticMessage={addOptimisticMessage}
sendMessage={sendMessage}
/>
{optimisticMessages.map((message, index) => (
<div key={index}>
<span>{message.text}</span>
{message.sending && (
<small>(Sending...)</small>
)}
</div>
))}
</div>
);
};
const deliverMessage = async (message) => {
// Simulate a delay
await new Promise((res) => setTimeout(res, 1000));
return message;
};
const Message = () => {
const [messages, setMessages] = useState([]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get('message'));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
};
export default Message;
In this code, we import useOptimistic
, useState
, and useEffect
from the react
package. We use the useOptimistic
hook in the Thread component to manage the displayed messages, and it takes two arguments, messages
, and an updater function. messages
(initial state) is an array of existing messages. The updater function receives the current state and the new message to add. useOptimistic
returns the updated state with the new message and a "sending" flag.
The messageForm
component handles user input and message submission, and inside it, we use formRef
to access the form element. Within the messageForm
component, we define formAction
which calls addOptimisticMessage
with the submitted message to add it optimistically to the displayed list, then clears the form after submission, and eventually calls sendMessage
to send the message to the server.
The Thread
component renders the message form and the message list and uses useOptimistic
to manage the displayed messages, including optimistic updates. Within the Thread
component, we map over optimisticMessages
to display each message with its text and a "Sending..." indicator if the sending flag is true.
We use the deliverMessage
function to simulate a delay of 1 second for the message delivery, and it returns the message for updating the state after the delay.
The Message
Component is our main component, and it manages the overall messages
state using useState
. Inside this component, we define sendMessage
, which calls deliverMessage
to send the message and update the state with the sent message after the delay. The Thread
component is nested within the Message
component, which renders it, passing the current message
state and the sendMessage
function.
8. Document Metadata
React 19 will provide built-in support for managing document metadata, which includes elements like titles, descriptions, and meta tags. These elements are necessary for the SEO optimization of websites. In the past, we had to use third-party libraries such as react-helmet
for handling document metadata. With this latest version of React, we no longer require the use of external libraries.
We can now put the title and meta tags anywhere within our component. React 19 automatically hoists the defined <title>
, <link>
, and <meta>
tags to the <head>
section of the document. This ensures proper placement and functionality regardless of rendering methods whether client-side or server-side.
const Home = () => {
return (
<>
<title>Yangy’s World</title>
<meta name="description" content="Freecode camp blogs" />
<h1>Hello World</h1>
</>
);
}
export default Home;
This code shows a React component , Home
, that includes document metadata elements <title>
and <meta>
. The <title>
tag gives the webpage the title, "Yangy’s World." The <meta>
tag creates a meta description for the webpage, which is a brief summary of the page's content.
9. Ref as a Prop
In React 19, we can now pass ref
directly as a prop to a functional component when using the useRef
hook. In times past, to pass a ref
to a child component, we had to first create the ref and then pass the ref as a prop to the child component, and then it could be accessed within the child component using forwardRef
. React 19 eliminates the need for forwardRef
and the example below will illustrate this major change:
Using forwardRef With the useRef Hook:
import React, { useRef, forwardRef } from 'react';
const Ref = () => {
const inputRef = useRef(null);
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
const InputWithRef = forwardRef((props, ref) => {
return <input type="text" ref={ref} />;
});
return (
<div>
<InputWithRef ref={inputRef} />
<button onClick={focusInput}>Focus the input</button>
</div>
);
};
export default Ref;
In this code, we import useRef
and forwardRef
from the react
package. We use the useRef
hook to create a ref
object, inputRef
, that can be attached to a DOM element. inputRef.current
holds a reference to the DOM element after it's mounted.
The focusInput
function defines a function to focus the input element and checks if inputRef.current
is not null (meaning the input element exists). If it exists, it calls the focus()
method on the element to set focus.
The Ref
component nests the InputWithRef
component and passes the inputRef
to it. It contains a button that calls the focusInput
function when clicked.
The InputWithRef
component uses forwardRef
to receive a ref
as a prop. The received ref
is then passed to the input element.
Now with React 19, we only have to make a minor alteration to the InputWithRef
component by passing ref
directly as a prop.
const InputWithRef = ({ props, ref }) => {
return <input type="text" ref={ref} />;
};
Conclusion
We have been able to cover React’s major features, and hopefully, we will all learn to use their full potential. The React compiler is a game-changer because it will handle performance optimization automatically, allowing developers to focus more on building features and less on performance. React 19 is a significant step forward for the React ecosystem. Once a developer can understand these new features, he/she will be able to create more performant, scalable, and user-friendly applications.
References
Neha, S. (2024, March 27). New Features in React 19 – Updates with Code Examples. FreeCodeCamp. https://www.freecodecamp.org/news/new-react-19-features/
Brad, T. [Traversy Media]. (Mar 6, 2024). Exploring React 19 Features. Youtube.
React Team. (2024, April 25). React 19 RC. Meta. react.dev/blog/2024/04/25/react-19