On April 25th, the React team officially announced the release of React 19 on NPM. Developers can now download and start using it.
Coincidentally, this blog is also built using Next.js, so we can take this opportunity to try out the changes introduced in React 19.
The official recommendation is to first install the stable version of React 18.3 to identify any potential hidden issues before upgrading to version 19.After verifying that everything is correct, you can proceed to install React 19 (currently in BETA) and start using the new APIs.Upgrade Guide for React v19
After a long period of silence, React has taken community feedback into account, addressing many pain points and optimizing the framework. The highly anticipated React 19 has been released.
Major changes include the following:
<form>
Tags and corresponding Hooks for form operations.use()
and more。The React Compiler can be said to be the most exciting feature in version 19. It is a new compiler designed to help developers optimize React code.For example, the series of optimization hooks such as useMemo and useCallback were previously cumbersome to use and could even lead to negative optimization. React has introduced the Compiler to automatically handle the code, avoiding the phenomenon of negative optimization.
In simple terms, the Compiler caches every element and function in the component, and only re-caches them when changes occur. Otherwise, it continues to use the cached values.
This article focuses on usage rather than principles. For more details, please refer to this article:I've Got React Compiler Down Pat
To enable React Compiler, we first need to perform a check on our project.
1npx react-compiler-healthcheck
2
This script is mainly used to check: 1. How many components in the project can be successfully optimized: the more, the better. 2. Whether strict mode is used, as using it increases the success rate of optimization. 3. Whether third-party libraries incompatible with the Compiler are used.
The results of this framework check are as follows:Different frameworks have different methods for using the Compiler. To enable the Compiler in Next.js, you need to first download the Next-canary version and babel-plugin-react-compiler.
1npm install next@canary babel-plugin-react-compiler
then in next.config.js
:
1// next.config.js
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4 experimental: {
5 reactCompiler: true,
6 },
7};
8
9module.exports = nextConfig;
10
you can start the compiler to optimize the project.After successful optimization, you will see Memo stars in React-dev-tool.
It is worth mentioning that the Compiler is still in testing and has many issues, such as conflicts with i18n client components, so it is still under observation.
React 19 introduces many new Hooks, includinguse()
,useOptimistic()
,useFormStatus()
, most of which are designed for Action, i.e.<form>
tags.。
Since I didn't use forms, I started with other new features first,like use()
。
Use to retrieve resource values, such as Promises or Context. Unlike other hooks, it can be used within if statements.
Its specific principles are as follows (taken from the official documentation):
When a Promise is called using the use API, it integrates with and Suspense error boundaries. When the Promise passed to use is in a pending state, the component calling use will suspend. If the component calling use is wrapped in a Suspense boundary, a fallback will be displayed. After resolving the Promise, the Suspense fallback will be replaced by the component rendering the data returned by the use API. If the Promise passed to use is rejected, the fallback of the nearest error boundary will be displayed.
Basic usage is as follows:
1const value = use(resource);
2
It is worth noting that:
Usage in this project is as follows:
1"use client";
2import { Suspense } from "react";
3import { GhostPointer } from "./GhostPointer";
4import { MyTypeWrite } from "./TypeWrite";
5import { DailyWord } from "@/utils/getDailyWord";
6import ErrorBoundary from "./ErrorBoundary";
7
8export function Banner({
9 language,
10 isGetDailyWord,
11 wordsFetch,
12}: {
13 wordsFetch?: Promise<DailyWord>;
14 language: string;
15 isGetDailyWord: boolean;
16}) {
17 return (
18 <ErrorBoundary
19 fallback={
20 <GhostPointer>
21 <span
22 style={{
23 display: "flex",
24 lineHeight: "250px",
25 fontSize: "4rem",
26 justifyContent: "center",
27 color: "white",
28 }}
29 >
30 ⚠️Something went wrong
31 </span>
32 </GhostPointer>
33 }
34 >
35 <Suspense
36 fallback={
37 <GhostPointer>
38 <span
39 style={{
40 display: "flex",
41 lineHeight: "250px",
42 fontSize: "4rem",
43 justifyContent: "center",
44 color: "white",
45 }}
46 >
47 Loading...
48 </span>
49 </GhostPointer>
50 }
51 >
52 <GhostPointer>
53 <MyTypeWrite
54 language={language}
55 wordsFetch={wordsFetch}
56 isGetDailyWord={isGetDailyWord}
57 />
58 </GhostPointer>
59 </Suspense>
60 </ErrorBoundary>
61 );
62}
63
Wrap the target component with ErrorBoundary and Suspense to provide corresponding UI during resolution and after resolution failure. Then pass the wordsFetch function from the server and resolve it using use.
1//layout
2import { Banner } from "../components/Banner";
3import { getDailyWord } from "@/utils/getDailyWord";
4
5export default async function FrontLayout({
6 children,
7 params: { language },
8}: {
9 children: React.ReactNode;
10 params: { language: string };
11}) {
12 const wordsFetch = getDailyWord();
13 return (
14 <div className="flex flex-col items-center">
15 <div className="w-[100vw]">
16 <Banner
17 language={language}
18 isGetDailyWord={true}
19 wordsFetch={wordsFetch}
20 ></Banner>
21 </div>
22 <section className="w-full">{children}</section>
23 </div>
24 );
25}
26//TypeWrite
27("use client");
28import { usePathname } from "next/navigation";
29import { ReactTyped } from "react-typed";
30import { getDailyWord } from "@/utils/getDailyWord";
31import { Suspense, use, useState } from "react";
32import { DailyWord } from "@/utils/getDailyWord";
33import { splitPathname } from "@/utils/dealPathname";
34import { useTranslation } from "@/app/i18n/client";
35export function MyTypeWrite({
36 language,
37 isGetDailyWord,
38 wordsFetch,
39}: {
40 language: string;
41 isGetDailyWord: boolean;
42 wordsFetch?: Promise<DailyWord>;
43}) {
44 let word;
45 const pathName = usePathname();
46 const title = splitPathname(pathName);
47 const { t } = useTranslation(language, "translations");
48 if (isGetDailyWord && wordsFetch) {
49 const words = use(wordsFetch);
50 word = language === "zh-CN" ? words.note : words.content;
51 }
52 return (
53 <ReactTyped
54 strings={!word ? [t(title)] : [word]}
55 typeSpeed={50}
56 style={{
57 display: "flex",
58 lineHeight: "250px",
59 fontSize: "4rem",
60 justifyContent: "center",
61 color: "white",
62 }}
63 />
64 );
65}
66
The final effect can be seen on the blog homepage (code is stored on GitHub).
React 19's updates go far beyond this. I have only used these two methods so far.
There are still many hooks related to optimistic updates, form operations, and more that I have yet to use.
The much-criticized Ref forwarding has also been optimized.
Error messages are now more user-friendly, and more.
I will gradually start using React 19 in production in the future.