Skip to content

Commit c276c58

Browse files
React 19 modernization (#815)
* React 19 modernization * Update dependencies (#817) * Update dependencies * dependency upgrades * set node version * use node 20 * Add CSSProperties and Ref reference pages (#816) * gen-readme
1 parent aeb9f51 commit c276c58

8 files changed

Lines changed: 462 additions & 401 deletions

File tree

README.md

Lines changed: 222 additions & 193 deletions
Large diffs are not rendered by default.

docs/advanced/patterns_by_usecase.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,20 +153,22 @@ export const defaultProps = <
153153

154154
_thanks [dmisdm](https://github.com/typescript-cheatsheets/react/issues/23)_
155155

156-
:new: You should also consider whether to explicitly forward refs:
156+
You should also consider whether to accept a `ref`. In React 19, `ref` is a regular prop on function components — no `forwardRef` needed:
157157

158158
```tsx
159-
import { forwardRef, ReactNode } from "react";
159+
import { Ref, ReactNode } from "react";
160160

161-
// base button, with ref forwarding
162-
type Props = { children: ReactNode; type: "submit" | "button" };
163-
export type Ref = HTMLButtonElement;
161+
type Props = {
162+
children: ReactNode;
163+
type: "submit" | "button";
164+
ref?: Ref<HTMLButtonElement>;
165+
};
164166

165-
export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
166-
<button ref={ref} className="MyCustomButtonClass" type={props.type}>
167-
{props.children}
167+
export const FancyButton = ({ ref, children, type }: Props) => (
168+
<button ref={ref} className="MyCustomButtonClass" type={type}>
169+
{children}
168170
</button>
169-
));
171+
);
170172
```
171173

172174
## Polymorphic Components (e.g. with `as` props)
Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,153 @@
11
---
22
id: concurrent
3-
title: Concurrent React/React Suspense
3+
title: Concurrent React
44
---
55

6-
_Not written yet._ watch [https://github.com/sw-yx/fresh-async-react](https://github.com/sw-yx/fresh-async-react) for more on React Suspense and Time Slicing.
6+
The Concurrent React APIs (`Suspense`, `useTransition`, `useDeferredValue`, `startTransition`, `use`) let you keep the UI responsive while React renders work in the background or waits for data. They're all stable as of React 18 and gained additional capabilities in React 19.
7+
8+
## `Suspense`
9+
10+
`Suspense` lets you declaratively show a fallback while a child component is waiting for something — typically data unwrapped with `use(promise)`, a lazy component, or a streamed boundary on the server.
11+
12+
```tsx
13+
import { Suspense } from "react";
14+
15+
const UserProfile = ({ userPromise }: { userPromise: Promise<User> }) => {
16+
const user = use(userPromise);
17+
return <p>Hello, {user.name}!</p>;
18+
};
19+
20+
const App = ({ userPromise }: { userPromise: Promise<User> }) => (
21+
<Suspense fallback={<p>Loading...</p>}>
22+
<UserProfile userPromise={userPromise} />
23+
</Suspense>
24+
);
25+
```
26+
27+
`SuspenseProps` is typed as `{ children?: ReactNode; fallback?: ReactNode }`. The fallback can be any `ReactNode`, including `null`.
28+
29+
## `use`
30+
31+
`use` reads the value of a context or a promise. Unlike `useContext`, it can be called inside conditions and loops, and it integrates with `Suspense` for promises.
32+
33+
```tsx
34+
import { use } from "react";
35+
36+
const Comments = ({
37+
commentsPromise,
38+
}: {
39+
commentsPromise: Promise<Comment[]>;
40+
}) => {
41+
// Suspends until the promise resolves; throws to the nearest <Suspense>.
42+
const comments = use(commentsPromise);
43+
return (
44+
<ul>
45+
{comments.map((c) => (
46+
<li key={c.id}>{c.text}</li>
47+
))}
48+
</ul>
49+
);
50+
};
51+
```
52+
53+
The promise is typically created by a parent and passed down — don't create it inside the component, or you'll create a new promise on every render.
54+
55+
## `useTransition`
56+
57+
`useTransition` marks a state update as non-urgent so React can keep typing, scrolling, and other urgent input responsive while it renders.
58+
59+
```tsx
60+
import { useState, useTransition } from "react";
61+
62+
const TabSwitcher = () => {
63+
const [isPending, startTransition] = useTransition();
64+
const [tab, setTab] = useState<"posts" | "comments">("posts");
65+
66+
const selectTab = (next: "posts" | "comments") => {
67+
startTransition(() => {
68+
setTab(next);
69+
});
70+
};
71+
72+
return (
73+
<>
74+
<button disabled={isPending} onClick={() => selectTab("posts")}>
75+
Posts
76+
</button>
77+
<button disabled={isPending} onClick={() => selectTab("comments")}>
78+
Comments
79+
</button>
80+
{tab === "posts" ? <Posts /> : <Comments />}
81+
</>
82+
);
83+
};
84+
```
85+
86+
### Async transitions (React 19)
87+
88+
In React 19, the function passed to `startTransition` can be async. This is the foundation for Actions and is how `useActionState` and `<form action>` schedule their pending state.
89+
90+
```tsx
91+
const [isPending, startTransition] = useTransition();
92+
93+
const onSubmit = () => {
94+
startTransition(async () => {
95+
await saveDraft(content);
96+
setSavedAt(new Date());
97+
});
98+
};
99+
```
100+
101+
`isPending` stays `true` for the entire duration of the async callback, including awaited work.
102+
103+
## `useDeferredValue`
104+
105+
`useDeferredValue` lets you defer re-rendering a part of the UI that's expensive to compute, so urgent updates (typing into an input) can flush first.
106+
107+
```tsx
108+
import { useDeferredValue, useState } from "react";
109+
110+
const SearchPage = () => {
111+
const [query, setQuery] = useState("");
112+
const deferredQuery = useDeferredValue(query);
113+
114+
return (
115+
<>
116+
<input value={query} onChange={(e) => setQuery(e.target.value)} />
117+
{/* SearchResults re-renders with deferredQuery, lagging behind input */}
118+
<SearchResults query={deferredQuery} />
119+
</>
120+
);
121+
};
122+
```
123+
124+
### `initialValue` (React 19)
125+
126+
React 19 added an optional second argument: the value to use during the initial render before the deferred value has caught up. Useful for SSR/streaming when you want to show a known initial value rather than the latest one.
127+
128+
```tsx
129+
const deferredQuery = useDeferredValue(query, "");
130+
```
131+
132+
## `startTransition` (standalone)
133+
134+
`startTransition` is also exported directly from `react` for use outside components — for example, inside event handlers in non-React code or third-party stores.
135+
136+
```tsx
137+
import { startTransition } from "react";
138+
139+
store.subscribe(() => {
140+
startTransition(() => {
141+
forceRender();
142+
});
143+
});
144+
```
145+
146+
The standalone version does not provide an `isPending` flag — use the hook if you need that.
147+
148+
## See also
149+
150+
- [`useActionState`, `useFormStatus`, `useOptimistic`](https://react.dev/reference/react) — built on top of transitions
151+
- [Server Components and `'use server'`](https://react.dev/reference/rsc/server-components)
7152

8153
[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new).

docs/basic/getting-started/context.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type ThemeContextType = "light" | "dark";
1515
const ThemeContext = createContext<ThemeContextType>("light");
1616
```
1717

18-
Wrap the components that need the context with a context provider:
18+
Wrap the components that need the context by rendering the context itself as a provider. In React 19, the context object can be rendered directly — you no longer need `<ThemeContext.Provider>`:
1919

2020
```tsx
2121
import { useState } from "react";
@@ -24,25 +24,29 @@ const App = () => {
2424
const [theme, setTheme] = useState<ThemeContextType>("light");
2525

2626
return (
27-
<ThemeContext.Provider value={theme}>
27+
<ThemeContext value={theme}>
2828
<MyComponent />
29-
</ThemeContext.Provider>
29+
</ThemeContext>
3030
);
3131
};
3232
```
3333

34-
Call `useContext` to read and subscribe to the context.
34+
> `<ThemeContext.Provider value={theme}>` still works and is identical in behavior — it's just the legacy spelling.
35+
36+
Read the context with `use`:
3537

3638
```tsx
37-
import { useContext } from "react";
39+
import { use } from "react";
3840

3941
const MyComponent = () => {
40-
const theme = useContext(ThemeContext);
42+
const theme = use(ThemeContext);
4143

4244
return <p>The current theme is {theme}.</p>;
4345
};
4446
```
4547

48+
> `useContext(ThemeContext)` still works too. The main difference is that `use` can also unwrap a promise, and it can be called inside conditions and loops.
49+
4650
## Without default context value
4751

4852
If you don't have any meaningful default value, specify `null`:
@@ -64,20 +68,20 @@ const App = () => {
6468
});
6569

6670
return (
67-
<CurrentUserContext.Provider value={currentUser}>
71+
<CurrentUserContext value={currentUser}>
6872
<MyComponent />
69-
</CurrentUserContext.Provider>
73+
</CurrentUserContext>
7074
);
7175
};
7276
```
7377

7478
Now that the type of the context can be `null`, you'll notice that you'll get a `'currentUser' is possibly 'null'` TypeScript error if you try to access the `username` property. You can use optional chaining to access `username`:
7579

7680
```tsx
77-
import { useContext } from "react";
81+
import { use } from "react";
7882

7983
const MyComponent = () => {
80-
const currentUser = useContext(CurrentUserContext);
84+
const currentUser = use(CurrentUserContext);
8185

8286
return <p>Name: {currentUser?.username}.</p>;
8387
};
@@ -86,7 +90,7 @@ const MyComponent = () => {
8690
However, it would be preferable to not have to check for `null`, since we know that the context won't be `null`. One way to do that is to provide a custom hook to use the context, where an error is thrown if the context is not provided:
8791

8892
```tsx
89-
import { createContext } from "react";
93+
import { createContext, use } from "react";
9094

9195
interface CurrentUserContextType {
9296
username: string;
@@ -95,11 +99,11 @@ interface CurrentUserContextType {
9599
const CurrentUserContext = createContext<CurrentUserContextType | null>(null);
96100

97101
const useCurrentUser = () => {
98-
const currentUserContext = useContext(CurrentUserContext);
102+
const currentUserContext = use(CurrentUserContext);
99103

100104
if (!currentUserContext) {
101105
throw new Error(
102-
"useCurrentUser has to be used within <CurrentUserContext.Provider>"
106+
"useCurrentUser has to be used within <CurrentUserContext>"
103107
);
104108
}
105109

@@ -110,8 +114,6 @@ const useCurrentUser = () => {
110114
Using a runtime type check in this will have the benefit of printing a clear error message in the console when a provider is not wrapping the components properly. Now it's possible to access `currentUser.username` without checking for `null`:
111115

112116
```tsx
113-
import { useContext } from "react";
114-
115117
const MyComponent = () => {
116118
const currentUser = useCurrentUser();
117119

@@ -124,10 +126,10 @@ const MyComponent = () => {
124126
Another way to avoid having to check for `null` is to use type assertion to tell TypeScript you know the context is not `null`:
125127

126128
```tsx
127-
import { useContext } from "react";
129+
import { use } from "react";
128130

129131
const MyComponent = () => {
130-
const currentUser = useContext(CurrentUserContext);
132+
const currentUser = use(CurrentUserContext);
131133

132134
return <p>Name: {currentUser!.username}.</p>;
133135
};

0 commit comments

Comments
 (0)