Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
415 changes: 222 additions & 193 deletions README.md

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions docs/advanced/patterns_by_usecase.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,20 +153,22 @@ export const defaultProps = <

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

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

```tsx
import { forwardRef, ReactNode } from "react";
import { Ref, ReactNode } from "react";

// base button, with ref forwarding
type Props = { children: ReactNode; type: "submit" | "button" };
export type Ref = HTMLButtonElement;
type Props = {
children: ReactNode;
type: "submit" | "button";
ref?: Ref<HTMLButtonElement>;
};

export const FancyButton = forwardRef<Ref, Props>((props, ref) => (
<button ref={ref} className="MyCustomButtonClass" type={props.type}>
{props.children}
export const FancyButton = ({ ref, children, type }: Props) => (
<button ref={ref} className="MyCustomButtonClass" type={type}>
{children}
</button>
));
);
```

## Polymorphic Components (e.g. with `as` props)
Expand Down
149 changes: 147 additions & 2 deletions docs/basic/getting-started/concurrent.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,153 @@
---
id: concurrent
title: Concurrent React/React Suspense
title: Concurrent React
---

_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.
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.

## `Suspense`

`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.

```tsx
import { Suspense } from "react";

const UserProfile = ({ userPromise }: { userPromise: Promise<User> }) => {
const user = use(userPromise);
return <p>Hello, {user.name}!</p>;
};

const App = ({ userPromise }: { userPromise: Promise<User> }) => (
<Suspense fallback={<p>Loading...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
```

`SuspenseProps` is typed as `{ children?: ReactNode; fallback?: ReactNode }`. The fallback can be any `ReactNode`, including `null`.

## `use`

`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.

```tsx
import { use } from "react";

const Comments = ({
commentsPromise,
}: {
commentsPromise: Promise<Comment[]>;
}) => {
// Suspends until the promise resolves; throws to the nearest <Suspense>.
const comments = use(commentsPromise);
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
);
};
```

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.

## `useTransition`

`useTransition` marks a state update as non-urgent so React can keep typing, scrolling, and other urgent input responsive while it renders.

```tsx
import { useState, useTransition } from "react";

const TabSwitcher = () => {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState<"posts" | "comments">("posts");

const selectTab = (next: "posts" | "comments") => {
startTransition(() => {
setTab(next);
});
};

return (
<>
<button disabled={isPending} onClick={() => selectTab("posts")}>
Posts
</button>
<button disabled={isPending} onClick={() => selectTab("comments")}>
Comments
</button>
{tab === "posts" ? <Posts /> : <Comments />}
</>
);
};
```

### Async transitions (React 19)

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.

```tsx
const [isPending, startTransition] = useTransition();

const onSubmit = () => {
startTransition(async () => {
await saveDraft(content);
setSavedAt(new Date());
});
};
```

`isPending` stays `true` for the entire duration of the async callback, including awaited work.

## `useDeferredValue`

`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.

```tsx
import { useDeferredValue, useState } from "react";

const SearchPage = () => {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);

return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{/* SearchResults re-renders with deferredQuery, lagging behind input */}
<SearchResults query={deferredQuery} />
</>
);
};
```

### `initialValue` (React 19)

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.

```tsx
const deferredQuery = useDeferredValue(query, "");
```

## `startTransition` (standalone)

`startTransition` is also exported directly from `react` for use outside components — for example, inside event handlers in non-React code or third-party stores.

```tsx
import { startTransition } from "react";

store.subscribe(() => {
startTransition(() => {
forceRender();
});
});
```

The standalone version does not provide an `isPending` flag — use the hook if you need that.

## See also

- [`useActionState`, `useFormStatus`, `useOptimistic`](https://react.dev/reference/react) — built on top of transitions
- [Server Components and `'use server'`](https://react.dev/reference/rsc/server-components)

[Something to add? File an issue](https://github.com/typescript-cheatsheets/react/issues/new).
36 changes: 19 additions & 17 deletions docs/basic/getting-started/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ThemeContextType = "light" | "dark";
const ThemeContext = createContext<ThemeContextType>("light");
```

Wrap the components that need the context with a context provider:
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>`:

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

return (
<ThemeContext.Provider value={theme}>
<ThemeContext value={theme}>
<MyComponent />
</ThemeContext.Provider>
</ThemeContext>
);
};
```

Call `useContext` to read and subscribe to the context.
> `<ThemeContext.Provider value={theme}>` still works and is identical in behavior — it's just the legacy spelling.

Read the context with `use`:

```tsx
import { useContext } from "react";
import { use } from "react";

const MyComponent = () => {
const theme = useContext(ThemeContext);
const theme = use(ThemeContext);

return <p>The current theme is {theme}.</p>;
};
```

> `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.

## Without default context value

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

return (
<CurrentUserContext.Provider value={currentUser}>
<CurrentUserContext value={currentUser}>
<MyComponent />
</CurrentUserContext.Provider>
</CurrentUserContext>
);
};
```

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`:

```tsx
import { useContext } from "react";
import { use } from "react";

const MyComponent = () => {
const currentUser = useContext(CurrentUserContext);
const currentUser = use(CurrentUserContext);

return <p>Name: {currentUser?.username}.</p>;
};
Expand All @@ -86,7 +90,7 @@ const MyComponent = () => {
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:

```tsx
import { createContext } from "react";
import { createContext, use } from "react";

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

const useCurrentUser = () => {
const currentUserContext = useContext(CurrentUserContext);
const currentUserContext = use(CurrentUserContext);

if (!currentUserContext) {
throw new Error(
"useCurrentUser has to be used within <CurrentUserContext.Provider>"
"useCurrentUser has to be used within <CurrentUserContext>"
);
}

Expand All @@ -110,8 +114,6 @@ const useCurrentUser = () => {
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`:

```tsx
import { useContext } from "react";

const MyComponent = () => {
const currentUser = useCurrentUser();

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

```tsx
import { useContext } from "react";
import { use } from "react";

const MyComponent = () => {
const currentUser = useContext(CurrentUserContext);
const currentUser = use(CurrentUserContext);

return <p>Name: {currentUser!.username}.</p>;
};
Expand Down
Loading
Loading