Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize viewTransiton and flushSync options #11989

Merged
merged 1 commit into from
Sep 12, 2024
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
7 changes: 7 additions & 0 deletions .changeset/stabilize-flush-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-router-dom": minor
"react-router": minor
"@remix-run/router": minor
---

Stabilize the `unstable_flushSync` option for navigations and fetchers
7 changes: 7 additions & 0 deletions .changeset/stabilize-view-transitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"react-router-dom": minor
"react-router": minor
"@remix-run/router": minor
---

Stabilize the `unstable_viewTransition` option for navigations and the corresponding `unstable_useViewTransitionState` hook
6 changes: 3 additions & 3 deletions docs/components/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface FormProps
reloadDocument?: boolean;
replace?: boolean;
state?: any;
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}
```

Expand Down Expand Up @@ -281,9 +281,9 @@ If you are using [`<ScrollRestoration>`][scrollrestoration], this lets you preve

See also: [`<Link preventScrollReset>`][link-preventscrollreset]

## `unstable_viewTransition`
## `viewTransition`

The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state].
The `viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`useViewTransitionState()`][use-view-transition-state].

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

Expand Down
16 changes: 8 additions & 8 deletions docs/components/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface LinkProps
reloadDocument?: boolean;
replace?: boolean;
state?: any;
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}

type To = string | Partial<Path>;
Expand Down Expand Up @@ -171,24 +171,24 @@ let { state } = useLocation();

The `reloadDocument` property can be used to skip client side routing and let the browser handle the transition normally (as if it were an `<a href>`).

## `unstable_viewTransition`
## `viewTransition`

The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`:
The `viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`:

```jsx
<Link to={to} unstable_viewTransition>
<Link to={to} viewTransition>
Click me
</Link>
```

If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state] hook (or check out the `transitioning` class and `isTransitioning` render prop in [NavLink][navlink]):
If you need to apply specific styles for this view transition, you will also need to leverage the [`useViewTransitionState()`][use-view-transition-state] hook (or check out the `transitioning` class and `isTransitioning` render prop in [NavLink][navlink]):

```jsx
function ImageLink(to) {
const isTransitioning =
unstable_useViewTransitionState(to);
useViewTransitionState(to);
return (
<Link to={to} unstable_viewTransition>
<Link to={to} viewTransition>
<p
style={{
viewTransitionName: isTransitioning
Expand All @@ -212,7 +212,7 @@ function ImageLink(to) {
}
```

<docs-warning>`unstable_viewTransition` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>
<docs-warning>`viewTransition` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

Expand Down
8 changes: 4 additions & 4 deletions docs/components/nav-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ When a `NavLink` is active it will automatically apply `<a aria-current="page">`

The `reloadDocument` property can be used to skip client side routing and let the browser handle the transition normally (as if it were an `<a href>`).

## `unstable_viewTransition`
## `viewTransition`

The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. By default, during the transition a `transitioning` class will be added to the `<a>` element that you can use to customize the view transition.
The `viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. By default, during the transition a `transitioning` class will be added to the `<a>` element that you can use to customize the view transition.

```css
a.transitioning p {
Expand All @@ -138,7 +138,7 @@ a.transitioning img {
```

```jsx
<NavLink to={to} unstable_viewTransition>
<NavLink to={to} viewTransition>
<p>Image Number {idx}</p>
<img src={src} alt={`Img ${idx}`} />
</NavLink>
Expand All @@ -147,7 +147,7 @@ a.transitioning img {
You may also use the `className`/`style` props or the render props passed to `children` to further customize based on the `isTransitioning` value.

```jsx
<NavLink to={to} unstable_viewTransition>
<NavLink to={to} viewTransition>
{({ isTransitioning }) => (
<>
<p
Expand Down
4 changes: 2 additions & 2 deletions docs/hooks/use-fetcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ If you find yourself calling this function inside of click handlers, you can pro

<docs-info>Any `fetcher.load` calls that are active on the page will be re-executed as part of revalidation (either after a navigation submission, another fetcher submission, or a `useRevalidator()` call)</docs-info>

#### `options.unstable_flushSync`
#### `options.flushSync`

The `unstable_flushSync` option tells React Router DOM to wrap the initial state update for this `fetcher.load` in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.
The `flushSync` option tells React Router DOM to wrap the initial state update for this `fetcher.load` in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

Expand Down
16 changes: 8 additions & 8 deletions docs/hooks/use-navigate.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ interface NavigateOptions {
state?: any;
preventScrollReset?: boolean;
relative?: RelativeRoutingType;
unstable_flushSync?: boolean;
unstable_viewTransition?: boolean;
flushSync?: boolean;
viewTransition?: boolean;
}

type RelativeRoutingType = "route" | "path";
Expand Down Expand Up @@ -114,19 +114,19 @@ new URL("..", window.origin + location.pathname + "/");
// 'https://remix.run/docs/en/main/start/'
```

## `options.unstable_flushSync`
## `options.flushSync`

The `unstable_flushSync` option tells React Router DOM to wrap the initial state update for this navigation in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.
The `flushSync` option tells React Router DOM to wrap the initial state update for this navigation in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.

<docs-warning>`unstable_flushSync` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>
<docs-warning>`flushSync` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

## `options.unstable_viewTransition`
## `options.viewTransition`

The `unstable_viewTransition` option enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state].
The `viewTransition` option enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`useViewTransitionState()`][use-view-transition-state].

<docs-warning>`unstable_viewTransition` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>
<docs-warning>`viewTransition` only works when using a data router, see [Picking a Router][picking-a-router]</docs-warning>

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

Expand Down
6 changes: 3 additions & 3 deletions docs/hooks/use-submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ Because submissions are navigations, the options may also contain the other navi
- `relative`
- `replace`
- `state`
- `unstable_viewTransition`
- `viewTransition`

### `options.unstable_flushSync`
### `options.flushSync`

The `unstable_flushSync` option tells React Router DOM to wrap the initial state update for this submission in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.
The `flushSync` option tells React Router DOM to wrap the initial state update for this submission in a [`ReactDOM.flushSync`][flush-sync] call instead of the default [`React.startTransition`][start-transition]. This allows you to perform synchronous DOM actions immediately after the update is flushed to the DOM.

<docs-warning>Please note that this API is marked unstable and may be subject to breaking changes without a major release</docs-warning>

Expand Down
15 changes: 7 additions & 8 deletions docs/hooks/use-view-transition-state.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: unstable_useViewTransitionState
title: useViewTransitionState
---

# `unstable_useViewTransitionState`
# `useViewTransitionState`

<details>
<summary>Type declaration</summary>

```tsx
declare function unstable_useViewTransitionState(
declare function useViewTransitionState(
to: To,
opts: { relative?: "route" : "path" } = {}
): boolean;
Expand All @@ -24,17 +24,16 @@ interface Path {

</details>

This hook returns `true` when there is an active [View Transition][view-transitions] to the specified location. This can be used to apply finer-grained styles to elements to further customize the view transition. This requires that view transitions have been enabled for the given navigation via the [unstable_viewTransition][link-view-transition] prop on the `Link` (or the `Form`, `navigate`, or `submit` call).
This hook returns `true` when there is an active [View Transition][view-transitions] to the specified location. This can be used to apply finer-grained styles to elements to further customize the view transition. This requires that view transitions have been enabled for the given navigation via the [viewTransition][link-view-transition] prop on the `Link` (or the `Form`, `navigate`, or `submit` call).

Consider clicking on an image in a list that you need to expand into the hero image on the destination page:

```jsx
function NavImage({ src, alt, id }) {
const to = `/images/${id}`;
const isTransitioning =
unstable_useViewTransitionState(to);
const isTransitioning = useViewTransitionState(to);
return (
<Link to={to} unstable_viewTransition>
<Link to={to} viewTransition>
<img
src={src}
alt={alt}
Expand All @@ -49,5 +48,5 @@ function NavImage({ src, alt, id }) {
}
```

[link-view-transition]: ../components/link#unstable_viewtransition
[link-view-transition]: ../components/link#viewtransition
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
4 changes: 2 additions & 2 deletions docs/routers/picking-a-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,5 @@ The following APIs are introduced in React Router 6.4 and will only work when us
[userouteloaderdata]: ../hooks/use-route-loader-data
[usesubmit]: ../hooks/use-submit
[useblocker]: ../hooks/use-blocker
[viewtransition-link]: ../components/link#unstable_viewtransition
[viewtransition-navigate]: ../hooks/use-navigate#optionsunstable_viewtransition
[viewtransition-link]: ../components/link#viewtransition
[viewtransition-navigate]: ../hooks/use-navigate#optionsviewtransition
Original file line number Diff line number Diff line change
Expand Up @@ -7405,13 +7405,13 @@ function testDomRouter(
return (
<div>
<Link to="/a">/a</Link>
<Link to="/b" unstable_viewTransition>
<Link to="/b" viewTransition>
/b
</Link>
<Form action="/c">
<button type="submit">/c</button>
</Form>
<Form action="/d" unstable_viewTransition>
<Form action="/d" viewTransition>
<button type="submit">/d</button>
</Form>
<Outlet />
Expand Down Expand Up @@ -7486,7 +7486,7 @@ function testDomRouter(
Component() {
return (
<>
<Link to="/page" unstable_viewTransition>
<Link to="/page" viewTransition>
/page
</Link>
<Outlet />
Expand Down
40 changes: 20 additions & 20 deletions packages/react-router-dom/__tests__/flush-sync-navigations-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe("flushSync", () => {
<>
<h1>About</h1>
<button
onClick={() => navigate("/", { unstable_flushSync: true })}
onClick={() => navigate("/", { flushSync: true })}
>
Go to /
</button>
Expand Down Expand Up @@ -61,14 +61,14 @@ describe("flushSync", () => {
expect(spy).toBeCalledTimes(1);
expect(spy).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({ unstable_flushSync: false })
expect.objectContaining({ flushSync: false })
);

fireEvent.click(screen.getByText("Go to /"));
await waitFor(() => screen.getByText("Home"));
expect(spy).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({ unstable_flushSync: true })
expect.objectContaining({ flushSync: true })
);

expect(spy).toBeCalledTimes(2);
Expand Down Expand Up @@ -110,7 +110,7 @@ describe("flushSync", () => {
onClick={() =>
submit(
{},
{ method: "post", action: "/", unstable_flushSync: true }
{ method: "post", action: "/", flushSync: true }
)
}
>
Expand Down Expand Up @@ -138,14 +138,14 @@ describe("flushSync", () => {
fireEvent.click(screen.getByText("Go to /about"));
await waitFor(() => screen.getByText("About"));
expect(spy).toBeCalledTimes(2);
expect(spy.mock.calls[0][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[1][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[0][1].flushSync).toBe(false);
expect(spy.mock.calls[1][1].flushSync).toBe(false);

fireEvent.click(screen.getByText("Go to /"));
await waitFor(() => screen.getByText("Home"));
expect(spy).toBeCalledTimes(4);
expect(spy.mock.calls[2][1].unstable_flushSync).toBe(true);
expect(spy.mock.calls[3][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[2][1].flushSync).toBe(true);
expect(spy.mock.calls[3][1].flushSync).toBe(false);

router.dispose();
});
Expand All @@ -167,7 +167,7 @@ describe("flushSync", () => {
<pre>{`async:${fetcher1.data}:${fetcher1.state}`}</pre>
<button
onClick={() =>
fetcher2.load("/fetch", { unstable_flushSync: true })
fetcher2.load("/fetch", { flushSync: true })
}
>
Load sync
Expand Down Expand Up @@ -202,14 +202,14 @@ describe("flushSync", () => {
fireEvent.click(screen.getByText("Load async"));
await waitFor(() => screen.getByText("async:LOADER:idle"));
expect(spy).toBeCalledTimes(2);
expect(spy.mock.calls[0][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[1][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[0][1].flushSync).toBe(false);
expect(spy.mock.calls[1][1].flushSync).toBe(false);

fireEvent.click(screen.getByText("Load sync"));
await waitFor(() => screen.getByText("sync:LOADER:idle"));
expect(spy).toBeCalledTimes(4);
expect(spy.mock.calls[2][1].unstable_flushSync).toBe(true);
expect(spy.mock.calls[3][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[2][1].flushSync).toBe(true);
expect(spy.mock.calls[3][1].flushSync).toBe(false);

router.dispose();
});
Expand Down Expand Up @@ -238,7 +238,7 @@ describe("flushSync", () => {
onClick={() =>
fetcher2.submit(
{},
{ method: "post", action: "/", unstable_flushSync: true }
{ method: "post", action: "/", flushSync: true }
)
}
>
Expand Down Expand Up @@ -267,16 +267,16 @@ describe("flushSync", () => {
fireEvent.click(screen.getByText("Submit async"));
await waitFor(() => screen.getByText("async:ACTION:idle"));
expect(spy).toBeCalledTimes(3);
expect(spy.mock.calls[0][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[1][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[2][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[0][1].flushSync).toBe(false);
expect(spy.mock.calls[1][1].flushSync).toBe(false);
expect(spy.mock.calls[2][1].flushSync).toBe(false);

fireEvent.click(screen.getByText("Submit sync"));
await waitFor(() => screen.getByText("sync:ACTION:idle"));
expect(spy).toBeCalledTimes(6);
expect(spy.mock.calls[3][1].unstable_flushSync).toBe(true);
expect(spy.mock.calls[4][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[5][1].unstable_flushSync).toBe(false);
expect(spy.mock.calls[3][1].flushSync).toBe(true);
expect(spy.mock.calls[4][1].flushSync).toBe(false);
expect(spy.mock.calls[5][1].flushSync).toBe(false);

router.dispose();
});
Expand Down
4 changes: 2 additions & 2 deletions packages/react-router-dom/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ interface SharedSubmitOptions {
/**
* Enable flushSync for this submission's state updates
*/
unstable_flushSync?: boolean;
flushSync?: boolean;
}

/**
Expand Down Expand Up @@ -225,7 +225,7 @@ export interface SubmitOptions extends FetcherSubmitOptions {
/**
* Enable view transitions on this submission navigation
*/
unstable_viewTransition?: boolean;
viewTransition?: boolean;
}

const supportedFormEncTypes: Set<FormEncType> = new Set([
Expand Down
Loading