11import * as React from "react" ;
2- import { Link , Outlet , useNavigate , useNavigation } from "react-router" ;
2+ import {
3+ Link ,
4+ Outlet ,
5+ useFetcher ,
6+ useNavigate ,
7+ useNavigation ,
8+ } from "react-router" ;
9+ import type { loader as randomNumberLoader } from "./api.random" ;
310
411export default function Transitions ( ) {
512 let navigate = useNavigate ( ) ;
13+ let fetcher = useFetcher < typeof randomNumberLoader > ( ) ;
614 let navigation = useNavigation ( ) ;
715 let [ pending , startTransition ] = React . useTransition ( ) ;
816 let [ count , setCount ] = React . useState ( 0 ) ;
917 let [ count2 , setCount2 ] = React . useState ( 0 ) ;
18+ let [ random , setRandom ] = React . useState ( 0 ) ;
19+
20+ React . useEffect ( ( ) => {
21+ if ( fetcher . data ) {
22+ let { randomNumber } = fetcher . data ;
23+ startTransition ( ( ) => setRandom ( randomNumber ) ) ;
24+ }
25+ } , [ fetcher . data ] ) ;
26+
1027 return (
1128 < >
1229 < h1 > Transitions</ h1 >
@@ -24,116 +41,196 @@ export default function Transitions() {
2441 < code > unstable_transitions=false</ code >
2542 </ a >
2643 </ nav >
27- < ul style = { { maxWidth : "600px" } } >
28- < li >
29- < button onClick = { ( ) => navigate ( "/transitions/slow" ) } >
30- Slow navigation with < code > navigate</ code >
31- </ button >
32- < ul >
33- < li >
34- In the current state, < code > useNavigate</ code > navigations are not
35- wrapped in < code > startTransition</ code > , so they don't play nice
36- with other transition-aware state updates
37- </ li >
44+
45+ < div
46+ style = { {
47+ display : "grid" ,
48+ gridTemplateColumns : "1fr 1fr" ,
49+ gap : 0 ,
50+ marginTop : "1rem" ,
51+ border : "1px solid #ccc" ,
52+ } }
53+ >
54+ < div style = { { padding : "1rem" } } >
55+ < h2 > Navigations</ h2 >
56+
57+ < div >
58+ < p
59+ style = { {
60+ color : pending ? "green" : "lightgrey" ,
61+ fontWeight : pending ? "bold" : "normal" ,
62+ } }
63+ >
64+ Local React Transition: { pending ? "Pending" : "Idle" }
65+ </ p >
66+ < p
67+ style = { {
68+ color : navigation . state !== "idle" ? "green" : "lightgrey" ,
69+ fontWeight : navigation . state !== "idle" ? "bold" : "normal" ,
70+ } }
71+ >
72+ React Router Navigation State: { navigation . state }
73+ </ p >
74+ </ div >
75+
76+ < ul style = { { maxWidth : "600px" } } >
3877 < li >
39- Fixed by{ " " }
40- < code >
41- <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
42- </ code >
78+ < button onClick = { ( ) => navigate ( "/transitions/slow" ) } >
79+ < code > navigate("/transitions/slow")</ code >
80+ </ button >
81+ < ul >
82+ < 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
86+ </ li >
87+ < li >
88+ Fixed by{ " " }
89+ < code >
90+ <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
91+ </ code >
92+ </ li >
93+ </ ul >
4394 </ li >
44- </ ul >
45- </ li >
4695
47- < li >
48- < button
49- onClick = { ( ) => navigate ( "/transitions/slow" , { flushSync : true } ) }
50- >
51- Slow navigation with < code > navigate + flushSync</ code >
52- </ button >
53- < ul >
5496 < li >
55- With the new flag, useNavigate automatically wraps the navigation
56- in < code > React.startTransition</ code > . Passing the{ " " }
57- < code > flushSync</ code > option will opt out of that and apply
58- < code > React.flushSync</ code > to the underlying state update
97+ < button
98+ onClick = { ( ) =>
99+ navigate ( "/transitions/slow" , { flushSync : true } )
100+ }
101+ >
102+ < code >
103+ navigate("/transitions/slow", { "{" } flushSync: true { "}" } )
104+ </ code >
105+ </ button >
106+ < ul >
107+ < 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
112+ </ li >
113+ </ ul >
59114 </ li >
60- </ ul >
61- </ li >
62115
63- < li >
64- < button
65- onClick = { ( ) => startTransition ( ( ) => navigate ( "/transitions/slow" ) ) }
66- >
67- Slow navigation with local < code > startTransition + navigate</ code >
68- </ button >
69- < ul >
70116 < li >
71- Once you wrap them in < code > startTransition</ code > , they play
72- nicely with those updates but they prevent our internal
73- mid-navigation state updates from surfacing
117+ < button
118+ onClick = { ( ) =>
119+ startTransition ( ( ) => navigate ( "/transitions/slow" ) )
120+ }
121+ >
122+ < code >
123+ startTransition(() => navigate("/transitions/slow")
124+ </ code >
125+ </ button >
126+ < ul >
127+ < li >
128+ Once you wrap them in < code > startTransition</ code > , they play
129+ nicely with those updates but they prevent our internal
130+ mid-navigation state updates from surfacing
131+ </ li >
132+ < li >
133+ Fixed by{ " " }
134+ < code >
135+ <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
136+ </ code >
137+ </ li >
138+ </ ul >
74139 </ li >
140+
75141 < li >
76- Fixed by{ " " }
77- < code >
78- <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
79- </ code >
142+ < Link to = "/transitions/slow" >
143+ <Link to="/transitions/slow" />
144+ </ Link >
145+ < ul >
146+ < li >
147+ In the current state, < code > <Link></ code > navigations
148+ are not wrapped in startTransition, so they don't play nice
149+ with other transition-aware state updates
150+ </ li >
151+ < li >
152+ Fixed by{ " " }
153+ < code >
154+ <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
155+ </ code >
156+ </ li >
157+ </ ul >
80158 </ li >
81- </ ul >
82- </ li >
83159
84- < li >
85- < Link to = "/transitions/slow" > Slow Navigation via <Link></ Link >
86- < ul >
87160 < li >
88- In the current state, < code > <Link></ code > navigations are
89- not wrapped in startTransition, so they don't play nice with other
90- transition-aware state updates
161+ < Link to = "/transitions/parent" > /transitions/parent</ Link >
91162 </ li >
92163 < li >
93- Fixed by{ " " }
94- < code >
95- <HydrateRouter unstable_transitions={ "{" } true{ "}" } />
96- </ code >
164+ < Link to = "/transitions/parent/child" >
165+ /transitions/parent/child
166+ </ Link >
97167 </ li >
98168 </ ul >
99- </ li >
169+ </ div >
100170
101- < li >
102- < Link to = "/transitions/parent" > /transitions/parent</ Link >
103- </ li >
104- < li >
105- < Link to = "/transitions/parent/child" > /transitions/parent/child</ Link >
106- </ li >
107- </ ul >
108- < div >
109- < p
110- style = { {
111- color : pending ? "green" : "black" ,
112- fontWeight : pending ? "bold" : "normal" ,
113- } }
114- >
115- React Transition: { pending ? "Pending" : "Idle" }
116- </ p >
117- < p
118- style = { {
119- color : navigation . state !== "idle" ? "green" : "black" ,
120- fontWeight : navigation . state !== "idle" ? "bold" : "normal" ,
121- } }
122- >
123- React Router Navigation State: { navigation . state }
124- </ p >
171+ < div style = { { padding : "1rem" } } >
172+ < div >
173+ < h2 > Fetchers</ h2 >
174+ < button onClick = { ( ) => fetcher . load ( "/api/random" ) } >
175+ fetcher.load("/api/random")
176+ </ button >
177+ < br />
178+ < br />
179+ < button
180+ onClick = { ( ) => startTransition ( ( ) => fetcher . load ( "/api/random" ) ) }
181+ >
182+ startTransition(() => fetcher.load("/api/random"))
183+ </ button >
184+ < p
185+ style = { {
186+ color : fetcher . state !== "idle" ? "green" : "lightgrey" ,
187+ fontWeight : fetcher . state !== "idle" ? "bold" : "normal" ,
188+ } }
189+ >
190+ Fetcher State: < strong > { fetcher . state } </ strong >
191+ </ p >
192+ < p
193+ style = { {
194+ color : fetcher . data != null ? "green" : "lightgrey" ,
195+ fontWeight : fetcher . data != null ? "bold" : "normal" ,
196+ } }
197+ >
198+ Fetcher Data:{ " " }
199+ { fetcher . data ? JSON . stringify ( fetcher . data ) : "null" }
200+ </ p >
201+ < p
202+ style = { {
203+ color : random !== 0 ? "green" : "lightgrey" ,
204+ fontWeight : random !== 0 ? "bold" : "normal" ,
205+ } }
206+ >
207+ Transition-aware fetcher data: { random }
208+ </ p >
209+ </ div >
210+
211+ < div >
212+ < h2 > Counters</ h2 >
213+ < button onClick = { ( ) => setCount ( ( c ) => c + 1 ) } >
214+ < code > setCount(c => c + 1)</ code >
215+ </ button > { " " }
216+ < span > Count = { count } </ span >
217+ < br />
218+ < br />
219+ < button
220+ onClick = { ( ) =>
221+ React . startTransition ( ( ) => setCount2 ( ( c ) => c + 1 ) )
222+ }
223+ >
224+ < code > startTransition(() => setCount2(c => c + 1))</ code >
225+ </ button > { " " }
226+ < span > Count2 = { count2 } </ span >
227+ </ div >
228+ </ div >
125229 </ div >
126- < button onClick = { ( ) => setCount ( ( c ) => c + 1 ) } >
127- Increment counter w/o transition { count }
128- </ button > { " " }
129- < button
130- onClick = { ( ) => React . startTransition ( ( ) => setCount2 ( ( c ) => c + 1 ) ) }
131- >
132- Increment counter w/transition { count2 }
133- </ button >
230+
134231 < Outlet />
135232 < p >
136- TODO: Is it possible to demonstrate the issue with
233+ TODO: Is it possible to demonstrate the issue with{ " " }
137234 < code > React.useSyncExternalStore</ code > where the global opt-out is
138235 needed?
139236 </ p >
0 commit comments