Skip to content

Commit

Permalink
Stabilize viewTransiton and flushSync options (#11989)
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Sep 12, 2024
1 parent 2dd13c6 commit 73fcb9b
Show file tree
Hide file tree
Showing 18 changed files with 143 additions and 130 deletions.
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

0 comments on commit 73fcb9b

Please sign in to comment.