Skip to content

Commit 2ecc0da

Browse files
committed
Fix issue with surfacing entire state through use optimistic
1 parent e088fb5 commit 2ecc0da

File tree

2 files changed

+79
-42
lines changed

2 files changed

+79
-42
lines changed

packages/react-router/lib/components.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,14 @@ const USE_OPTIMISTIC = "useOptimistic";
8686
// @ts-expect-error Needs React 19 types but we develop against 18
8787
const useOptimisticImpl = React[USE_OPTIMISTIC];
8888

89-
function useOptimisticSafe(val: unknown) {
89+
function useOptimisticSafe<T>(
90+
val: T,
91+
): [T, React.Dispatch<React.SetStateAction<T>>] {
9092
if (useOptimisticImpl) {
9193
// eslint-disable-next-line react-hooks/rules-of-hooks
9294
return useOptimisticImpl(val);
9395
} else {
94-
// eslint-disable-next-line react-hooks/rules-of-hooks
95-
return React.useState(val);
96+
return [val, () => undefined];
9697
}
9798
}
9899

@@ -645,7 +646,7 @@ export function RouterProvider({
645646
} else {
646647
React.startTransition(() => {
647648
if (unstable_transitions === true) {
648-
setOptimisticState(newState);
649+
setOptimisticState((s) => getOptimisticRouterState(s, newState));
649650
}
650651
logErrorsAndSetState(newState);
651652
});
@@ -747,7 +748,7 @@ export function RouterProvider({
747748
} else {
748749
React.startTransition(() => {
749750
if (unstable_transitions === true) {
750-
setOptimisticState(newState);
751+
setOptimisticState((s) => getOptimisticRouterState(s, newState));
751752
}
752753
logErrorsAndSetState(newState);
753754
});
@@ -865,6 +866,33 @@ export function RouterProvider({
865866
);
866867
}
867868

869+
function getOptimisticRouterState(
870+
currentState: RouterState,
871+
newState: RouterState,
872+
): RouterState {
873+
return {
874+
// Don't surface "current location specific" stuff mid-navigation
875+
// (historyAction, location, matches, loaderData, errors, initialized,
876+
// restoreScroll, preventScrollReset, blockers, etc.)
877+
...currentState,
878+
// Only surface "pending/in-flight stuff"
879+
// (navigation, revalidation, actionData, fetchers, )
880+
navigation:
881+
newState.navigation.state !== "idle"
882+
? newState.navigation
883+
: currentState.navigation,
884+
revalidation:
885+
newState.revalidation !== "idle"
886+
? newState.revalidation
887+
: currentState.revalidation,
888+
actionData:
889+
newState.navigation.state !== "submitting"
890+
? newState.actionData
891+
: currentState.actionData,
892+
fetchers: newState.fetchers,
893+
};
894+
}
895+
868896
// Memoize to avoid re-renders when updating `ViewTransitionContext`
869897
const MemoizedDataRoutes = React.memo(DataRoutes);
870898

playground/react-transitions/app/routes/transitions.tsx

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -80,61 +80,59 @@ export default function Transitions() {
8080
</button>
8181
<ul>
8282
<li>
83-
In the current state, <code>useNavigate</code> navigations are
84-
not wrapped in <code>startTransition</code>, so they don't
85-
play nice with other transition-aware state updates
83+
In the current state, <code>useNavigate</code> doesn't wrap
84+
the navigation in <code>startTransition</code>, so they don't
85+
play nice with other transition-aware state updates (try
86+
updating the transition-aware counter mid-navigation)
8687
</li>
8788
<li>
88-
Fixed by{" "}
89-
<code>
90-
&lt;HydrateRouter unstable_transitions={"{"}true{"}"} /&gt;
91-
</code>
89+
With the new flag, they are wrapped in{" "}
90+
<code>startTransition</code>
9291
</li>
9392
</ul>
9493
</li>
9594

9695
<li>
9796
<button
9897
onClick={() =>
99-
navigate("/transitions/slow", { flushSync: true })
98+
// @ts-expect-error Needs React 19 types
99+
startTransition(() => navigate("/transitions/slow"))
100100
}
101101
>
102102
<code>
103-
navigate("/transitions/slow", {"{"} flushSync: true {"}"})
103+
startTransition(() =&gt; navigate("/transitions/slow")
104104
</code>
105105
</button>
106106
<ul>
107107
<li>
108-
With the new flag, useNavigate automatically wraps the
109-
navigation in <code>React.startTransition</code>. Passing the{" "}
110-
<code>flushSync</code> option will opt out of that and apply
111-
<code>React.flushSync</code> to the underlying state update
108+
If you wrap them in <code>startTransition</code> manually,
109+
they play nicely with those updates but they prevent our
110+
internal mid-navigation state updates from surfacing
111+
</li>
112+
<li>
113+
That can be fixed by enabling <code>useOptimistic</code>{" "}
114+
inside <code>&lt;RouterProvider&gt;</code>
112115
</li>
113116
</ul>
114117
</li>
115118

116119
<li>
117120
<button
118121
onClick={() =>
119-
// @ts-expect-error Needs React 19 types
120-
startTransition(() => navigate("/transitions/slow"))
122+
navigate("/transitions/slow", { flushSync: true })
121123
}
122124
>
123125
<code>
124-
startTransition(() =&gt; navigate("/transitions/slow")
126+
navigate("/transitions/slow", {"{"} flushSync: true {"}"})
125127
</code>
126128
</button>
127129
<ul>
128130
<li>
129-
Once you wrap them in <code>startTransition</code>, they play
130-
nicely with those updates but they prevent our internal
131-
mid-navigation state updates from surfacing
132-
</li>
133-
<li>
134-
Fixed by{" "}
135-
<code>
136-
&lt;HydrateRouter unstable_transitions={"{"}true{"}"} /&gt;
137-
</code>
131+
Once <code>useNavigate</code> is wrapped automatically,
132+
passing the
133+
<code>flushSync</code> option will opt out of{" "}
134+
<code>startTransition</code> and apply
135+
<code>React.flushSync</code> to the underlying state update
138136
</li>
139137
</ul>
140138
</li>
@@ -146,25 +144,36 @@ export default function Transitions() {
146144
<ul>
147145
<li>
148146
In the current state, <code>&lt;Link&gt;</code> navigations
149-
are not wrapped in startTransition, so they don't play nice
150-
with other transition-aware state updates
147+
are not wrapped in <code>startTransition</code>, so they don't
148+
play nice with other transition-aware state updates
151149
</li>
152150
<li>
153-
Fixed by{" "}
154-
<code>
155-
&lt;HydrateRouter unstable_transitions={"{"}true{"}"} /&gt;
156-
</code>
151+
With the new flag, they are wrapped in{" "}
152+
<code>startTransition</code>
157153
</li>
158154
</ul>
159155
</li>
160156

161157
<li>
162-
<Link to="/transitions/parent">/transitions/parent</Link>
163-
</li>
164-
<li>
165-
<Link to="/transitions/parent/child">
166-
/transitions/parent/child
167-
</Link>
158+
Nested Parent/Child Await:
159+
<ul>
160+
<li>
161+
Should not re-fallback when going from parent -&gt; child
162+
</li>
163+
<li>
164+
<span style={{ color: "red" }}>
165+
But for some reason they are when we enable the flag?
166+
</span>
167+
</li>
168+
<li>
169+
<Link to="/transitions/parent">/transitions/parent</Link>
170+
</li>
171+
<li>
172+
<Link to="/transitions/parent/child">
173+
/transitions/parent/child
174+
</Link>
175+
</li>
176+
</ul>
168177
</li>
169178
</ul>
170179
</div>

0 commit comments

Comments
 (0)