React 19: A Closer Look at the Latest Changes
A comprehensive tutorial on the new features of React 19, including detailed explanations of its capabilities and demonstrations showcasing its functionality.
Welcome to React 19, where innovation and simplicity come together! React 19 is on its way, bringing fresh improvements to front-end development.
As developers, we’re used to manually optimizing performance by memoizing components to prevent unnecessary re-renders. But with React 19, things are changing. This will be the major change in React 19, and there are many other improvements too.
In this blog post, you’ll explore the latest enhancements in React 19, giving you the power to create great web applications.
Table of Contents
- React Optimising Compiler (Automatic Memoization)
- use() Hook
- Server and Client Directives
- Form Actions
- useFormState() Hook
- useFormStatus() Hook
- useOptimistic() Hook
- Document Metadata
- Simplified Ref Handling
React Optimising Compiler
Previously, we (developers) relied on useMemo
, useCallback
, and memo
APIs for performance optimization. However, React’s new automatic reactivity compiler, known as React-Forgot changes the game. This smart compiler dynamically manages re-renders, determining when and how to update both the state and UI.
As a result, the need for manual optimization using useMemo
, useCallback
, and memo
APIs is eliminated, simplifying development while enhancing performance.
use() Hook
The use
hook is a powerful tool in React that allows us to load resources asynchronously, particularly fetching data.
It tracks the loading state using suspense and handles errors with error boundaries.
However, it’s essential to note that the use
hook doesn't offer a comprehensive solution for handling asynchronous state. Notably, it lacks support for query promise cancellation. Additionally, developers must handle loading and error states at the parent level rather than within the same component, and explicit access to the loading and error variables isn't provided.
Below is an example code demonstrating data fetching using the use
hook.
const fetchPosts = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
return res.json();
};
const Posts = () => {
const [isFetchPost, setIsFetchPost] = useState(false);
let data;
if (isFetchPost) {
data = use<Post[]>(fetchPosts());
}
return (
<div>
<Title>use() Example</Title>
<Button onClick={() => setIsFetchPost(prev => !prev)}>Fetch Posts</Button>
{data &&
data?.map(data => {
return <Card>{data.body}</Card>;
})}
</div>
);
};
const UseExample = () => {
return (
<Suspense fallback={<Title>Loading...</Title>}>
<Posts />
</Suspense>
);
};
Another area where we can utilize the new hook is with Context. Instead of using useContext()
, we can simplify it to something like use(context)
.
Below is an example code demonstrating reading context value using use
hook.
type Theme = "light" | "dark";
type ThemeContextValue = {
theme: Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextValue | null>(null);
const ThemeProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [theme, setTheme] = useState<Theme>("light");
const toggleTheme = () => {
setTheme(theme => (theme === "dark" ? "light" : "dark"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const useTheme = () => {
const themeContextValue = use(ThemeContext);
if (!themeContextValue) {
throw new Error("Please wrap component within the theme provider");
}
return themeContextValue;
};
const ThemeExample = () => {
const { theme, toggleTheme } = useTheme();
return (
<div>
<Title level={5}>Current Theme :{theme}</Title>
<Button onClick={toggleTheme}>Toggle Theme</Button>
</div>
);
};
const UseExample2 = () => {
return (
<ThemeProvider>
<Title>use() Example (context)</Title>
<ThemeExample />
</ThemeProvider>
);
};
Server and Client Directives
React now includes built-in support for server and client directives. Components can now be rendered on the server, leading to improved performance, faster page load times, and simplified data fetching with server components.
use server
‘use server’ makes the server-side functions that can be called from client-side code.
async function addToCart(data) {
'use server';
// ...
}
use client
‘use client’ lets you mark what code runs on the client.
'use client';
import { useState } from 'react';
import { formatDate } from './formatters';
import Button from './button';
export default function RichTextEditor({ timestamp, text }) {
const date = formatDate(timestamp);
// ...
const editButton = <Button />;
// ...
}
As demonstrated above, we can include these directives in our component file, allowing us to seamlessly integrate client- and server-side code within the same file.
Of course, we’ll need a server to utilize the server directive, but this represents a significant advancement for React.
Form Action
Form actions in React simplify working with forms, as now we can attach actions directly without needing onSubmit, similar to how we used to do it in PHP. Additionally, with the introduction of server components, actions can be executed on both the server and client sides managing both asynchronous and synchronous operations.
Let’s explore an example with some code.
type FieldType = {
title?: string;
body?: string;
};
type CreatePostProps = {
createPost: (post: FieldType) => void;
};
const CreatePost: React.FC<CreatePostProps> = ({ createPost }) => {
const addPost = (formData: FormData) => {
const newPost = {
title: formData.get("title") || "",
body: formData.get("body") || "",
} as FieldType;
createPost(newPost);
};
return (
<form style={{ maxWidth: 600 }} autoComplete="off" action={addPost}>
<Form.Item<FieldType> label="Title" name="title">
<Input name="title" />
</Form.Item>
<Form.Item<FieldType> label="Content" name="body">
<Input name="body" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</form>
);
};
const ActionExample = () => {
const [post, _addPost] = useState<FieldType[]>([]);
const addPost = (post: FieldType) => {
_addPost(prev => [...prev, post]);
};
return (
<div>
<Title>Form Action Example</Title>
<CreatePost createPost={addPost} />
<Divider />
{post &&
post?.map(post => {
return (
<Card>
<Title>{post?.title}</Title>
{post?.body}
</Card>
);
})}
</div>
);
};
export default ActionExample;
useFormState() Hook
The useFormState
hook keeps the state updated based on the result of form actions. It requires passing the form action function along with an initial state. The hook returns a new action and the latest form state, facilitating seamless form management.
The latest form state is also passed to the function you provided.
const [state, formAction] = useFormState(fn, initialState, permalink?);
Parameters
fn
: This is the function called when the form is submitted, or a button is pressed. It receives the previous form state initially set toinitialState
, and its previous return value, followed by any additional arguments expected by the form action.initialState
: This is the initial state value we want to set. It can be any serializable value. After the first invocation of the action, this parameter is ignored.optional permalink
: This optional parameter contains a unique page URL modified by the form. It's useful for pages with dynamic content (like feeds) and progressive enhancement.
Returns
The current state
: Initially matchesinitialState
. After the action is invoked, it matches the value returned by the action.A new action
: This can be passed as theaction
prop to our form component or as theformAction
prop to any button component within the form.
Now, let’s explore an example with some code.
const CreatePost: React.FC<CreatePostProps> = ({ createPost }) => {
const addPost = (prevState: string, queryData: FormData) => {
const newPost = {
title: queryData.get("title") || "",
body: queryData.get("body") || "",
} as FieldType;
if (newPost?.title && newPost?.title) {
createPost(newPost);
return "Post Created Successfully !";
} else {
return "Please enter all the filed";
}
};
const [state, formAction] = useFormState<string, FormData>(addPost, "");
return (
<form style={{ maxWidth: 600 }} autoComplete="off" action={formAction}>
<Form.Item<FieldType> label="Title" name="title">
<Input name="title" />
</Form.Item>
<Form.Item<FieldType> label="Content" name="body">
<Input name="body" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
<div>{state}</div>
</form>
);
};
useFormStatus() Hook
The useFormStatus Hook provides status information of the last form submission.
To access status information, the submit component needs to be rendered within a <form>. The Hook provides details such as the pending property, indicating whether the form is currently submitting.
const { pending, data, method, action } = useFormStatus();
Parameters
useFormStatus
does not take any parameters.
Returns
A status
object with the following properties:
pending
: A boolean indicating if the parent<form>
is pending submission.data
: An object implementing theFormData
interface that contains the data the parent<form>
is submitting.method
: A string value of either'get'
or'post'
. This represents whether the parent<form>
is submitting with either aGET
orPOST
HTTP method.action
: A reference to the function passed to theaction
prop on the parent<form>
. If there is no parent<form>
, the property isnull
. If there is a URI value provided to theaction
prop, or noaction
prop specified,status.action
will benull
.
Now, let’s explore an example with some code.
const CreatePostButton = () => {
const { action, data, method, pending } = useFormStatus();
console.log({ action, data, method, pending });
return (
<>
<Button type="primary" htmlType="submit">
Submit
</Button>
<div>{pending && "Loading..."}</div>
<div>{method && `Method:${method?.toUpperCase()}`}</div>
</>
);
};
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const CreatePost: React.FC<CreatePostProps> = ({ createPost }) => {
const addPost = async (formData: FormData) => {
await delay(1500);
const newPost = {
title: formData.get("title") || "",
body: formData.get("body") || "",
} as FieldType;
createPost(newPost);
};
return (
<form style={{ maxWidth: 600 }} autoComplete="off" action={addPost}>
<Form.Item<FieldType> label="Title" name="title">
<Input name="title" />
</Form.Item>
<Form.Item<FieldType> label="Content" name="body">
<Input name="body" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<CreatePostButton />
</Form.Item>
</form>
);
};
useOptimistic Hook()
useOptimistic is a React Hook that allows us to display a different state while waiting for an asynchronous action, like a network request. It takes in some initial state and returns a modified version known as the optimistic state.
Here, we provide a function that defines how the optimistic state should change based on the current state and action input while the action is in progress.
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
Parameters
state
: The initial value to be returned and used when no action is pending.updateFn(currentState, optimisticValue)
: A pure function that takes the current state and an optimistic value, returning the resulting optimistic state. The updateFn merges the currentState with the optimisticValue to determine the new optimistic state.
Returns
optimisticState
: The resulting optimistic state. It equals the state when no action is pending. During a pending action, it reflects the value returned by updateFn.addOptimistic
: A dispatching function used to trigger optimistic updates. It accepts one argument, optimisticValue, which is passed to updateFn along with the current state to compute the new optimistic state.
Now, let’s explore an example with some code.
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const CreatePost: React.FC<CreatePostProps> = ({ createPost, posts }) => {
const addPost = async (formData: FormData) => {
const newPost = {
title: formData.get("title") || "",
body: formData.get("body") || "",
} as FieldType;
//adding optimistic add post
addOptimisticPost({ title: newPost.title, body: newPost.body });
//add post(time taking server call)
await createPost(newPost);
};
const [optimisticPosts, addOptimisticPost] = useOptimistic<
FieldType[],
{ title: string; body: string }
>(posts, (state, newPost) => [
...state,
{
title: newPost.title,
body: newPost.body,
success: true,
},
]);
return (
<>
<form style={{ maxWidth: 600 }} autoComplete="off" action={addPost}>
<Form.Item<FieldType> label="Title" name="title">
<Input name="title" />
</Form.Item>
<Form.Item<FieldType> label="Content" name="body">
<Input name="body" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</form>
<Divider />
<RenderPosts posts={optimisticPosts} />
</>
);
};
type RenderPostsType = {
posts: FieldType[];
};
const RenderPosts: React.FC<RenderPostsType> = ({ posts }) => {
return (
<>
{posts &&
posts?.map(post => {
return (
<Card key={post.title}>
<Title>{post?.title}</Title>
{post?.body}
<div>
Status: {post.success ? "Posting..." : "Successfully Posted !"}
</div>
</Card>
);
})}
</>
);
};
const UseFormStatusExample = () => {
const [post, _addPost] = useState<FieldType[]>([]);
const addPost = async (post: FieldType) => {
await delay(1000);
_addPost(prev => [...prev, post]);
};
return (
<>
<Title>useOptimistic Example</Title>
<CreatePost createPost={addPost} posts={post} />
</>
);
};
export default UseFormStatusExample;
Document Metadata
Optimizing SEO is crucial for web applications, and managing page titles and meta tags is pivotal. Traditionally, developing React apps required additional coding or reliance on third-party packages for this task.
However, with the release of React 19, we gain direct control over page titles and meta tags within React components. This groundbreaking enhancement streamlines SEO management, empowering us to enhance website visibility and performance.
Let’s explore this feature with an example.
import { Title } from "antd";
const DocumentMetaDataExample = () => {
return (
<>
<title>Simform Blog</title>
<meta name="description" content="Simform Engineering Blog" />
<Title>Document Metadata Example </Title>
</>
);
};
export default DocumentMetaDataExample;
Simplified Ref Handling
In previous versions of React, we (developers) often relied on the forwardRef hook to access refs. However, with the introduction of React 19, this process has been streamlined. Now, refs are passed as props, eliminating the need for the forwardRef hook. This improvement simplifies code and enhances readability, making ref handling more intuitive in React development.
type ButtonProps = {
ref: MutableRefObject<HTMLButtonElement | null>;
incrementCount: () => void;
};
const Button: React.FC<ButtonProps> = ({ ref, incrementCount }) => {
return (
<button ref={ref} onClick={incrementCount}>
Increment
</button>
);
};
const RefHandlingExample = () => {
const [counter, dispatch] = useReducer(state => state + 1, 0);
const buttonRef = useRef<HTMLButtonElement | null>(null);
return (
<>
<Title>Ref Handling Example</Title>
<div>Counter: {counter}</div>
<Button ref={buttonRef} incrementCount={dispatch} />
</>
);
};
You can access the full source code on the GitHub Repository 📌
You can see the demo here.
Conclusion
React 19 is a big step forward in front-end development, combining innovation and simplicity to empower developers.
With automatic memoization, improved hooks, directives for both server and client, easier handling of refs, and streamlined document metadata management, it sets the stage for a new era of front-end development.
As we eagerly await the official release of React 19, let’s embrace these enhancements and embark on an exciting journey of exploration and innovation in the ever-evolving world of front-end development.
Happy coding!
For more updates on the latest tools and technologies, follow the Simform Engineering blog.