@@ -74,31 +74,37 @@ describe('useMemoCache()', () => {
7474 let setX ;
7575 let forceUpdate ;
7676 function Component ( props ) {
77- const cache = useMemoCache ( 4 ) ;
77+ const cache = useMemoCache ( 5 ) ;
7878
7979 // x is used to produce a `data` object passed to the child
8080 const [ x , _setX ] = useState ( 0 ) ;
8181 setX = _setX ;
82- const c_x = x !== cache [ 0 ] ;
83- cache [ 0 ] = x ;
8482
8583 // n is passed as-is to the child as a cache breaker
8684 const [ n , setN ] = useState ( 0 ) ;
8785 forceUpdate = ( ) => setN ( a => a + 1 ) ;
88- const c_n = n !== cache [ 1 ] ;
89- cache [ 1 ] = n ;
9086
87+ const c_0 = x !== cache [ 0 ] ;
9188 let data ;
92- if ( c_x ) {
93- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
89+ if ( c_0 ) {
90+ data = { text : `Count ${ x } ` } ;
91+ cache [ 0 ] = x ;
92+ cache [ 1 ] = data ;
9493 } else {
95- data = cache [ 2 ] ;
94+ data = cache [ 1 ] ;
9695 }
97- if ( c_x || c_n ) {
98- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
96+ const c_2 = x !== cache [ 2 ] ;
97+ const c_3 = n !== cache [ 3 ] ;
98+ let t0 ;
99+ if ( c_2 || c_3 ) {
100+ t0 = < Text data = { data } n = { n } /> ;
101+ cache [ 2 ] = x ;
102+ cache [ 3 ] = n ;
103+ cache [ 4 ] = t0 ;
99104 } else {
100- return cache [ 3 ] ;
105+ t0 = cache [ 4 ] ;
101106 }
107+ return t0 ;
102108 }
103109 let data ;
104110 const Text = jest . fn ( function Text ( props ) {
@@ -135,132 +141,117 @@ describe('useMemoCache()', () => {
135141
136142 // @gate enableUseMemoCacheHook
137143 test ( 'update component using cache with setstate during render' , async ( ) => {
138- let setX ;
139144 let setN ;
140145 function Component ( props ) {
141- const cache = useMemoCache ( 4 ) ;
146+ const cache = useMemoCache ( 5 ) ;
142147
143148 // x is used to produce a `data` object passed to the child
144- const [ x , _setX ] = useState ( 0 ) ;
145- setX = _setX ;
146- const c_x = x !== cache [ 0 ] ;
147- cache [ 0 ] = x ;
149+ const [ x ] = useState ( 0 ) ;
150+
151+ const c_0 = x !== cache [ 0 ] ;
152+ let data ;
153+ if ( c_0 ) {
154+ data = { text : `Count ${ x } ` } ;
155+ cache [ 0 ] = x ;
156+ cache [ 1 ] = data ;
157+ } else {
158+ data = cache [ 1 ] ;
159+ }
148160
149161 // n is passed as-is to the child as a cache breaker
150162 const [ n , _setN ] = useState ( 0 ) ;
151163 setN = _setN ;
152- const c_n = n !== cache [ 1 ] ;
153- cache [ 1 ] = n ;
154164
155- // NOTE: setstate and early return here means that x will update
156- // without the data value being updated. Subsequent renders could
157- // therefore think that c_x = false (hasn't changed) and skip updating
158- // data.
159- // The memoizing compiler will have to handle this case, but the runtime
160- // can help by falling back to resetting the cache if a setstate occurs
161- // during render (this mirrors what we do for useMemo and friends)
162165 if ( n === 1 ) {
163166 setN ( 2 ) ;
164167 return ;
165168 }
166169
167- let data ;
168- if ( c_x ) {
169- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
170+ const c_2 = x !== cache [ 2 ] ;
171+ const c_3 = n !== cache [ 3 ] ;
172+ let t0 ;
173+ if ( c_2 || c_3 ) {
174+ t0 = < Text data = { data } n = { n } /> ;
175+ cache [ 2 ] = x ;
176+ cache [ 3 ] = n ;
177+ cache [ 4 ] = t0 ;
170178 } else {
171- data = cache [ 2 ] ;
172- }
173- if ( c_x || c_n ) {
174- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
175- } else {
176- return cache [ 3 ] ;
179+ t0 = cache [ 4 ] ;
177180 }
181+ return t0 ;
178182 }
179183 let data ;
180184 const Text = jest . fn ( function Text ( props ) {
181185 data = props . data ;
182- return data . text ;
186+ return ` ${ data . text } (n= ${ props . n } )` ;
183187 } ) ;
184188
185189 const root = ReactNoop . createRoot ( ) ;
186190 await act ( ( ) => {
187191 root . render ( < Component /> ) ;
188192 } ) ;
189- expect ( root ) . toMatchRenderedOutput ( 'Count 0' ) ;
193+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=0) ' ) ;
190194 expect ( Text ) . toBeCalledTimes ( 1 ) ;
191195 const data0 = data ;
192196
193- // Simultaneously trigger an update to x (should create a new data value)
194- // and trigger the setState+early return. The runtime should reset the cache
195- // to avoid an inconsistency
197+ // Trigger an update that will cause a setState during render. The `data` prop
198+ // does not depend on `n`, and should remain cached.
196199 await act ( ( ) => {
197- setX ( 1 ) ;
198200 setN ( 1 ) ;
199201 } ) ;
200- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
202+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=2) ' ) ;
201203 expect ( Text ) . toBeCalledTimes ( 2 ) ;
202- expect ( data ) . not . toBe ( data0 ) ;
203- const data1 = data ;
204-
205- // Forcing an unrelated update shouldn't recreate the
206- // data object.
207- await act ( ( ) => {
208- setN ( 3 ) ;
209- } ) ;
210- expect ( root ) . toMatchRenderedOutput ( 'Count 1' ) ;
211- expect ( Text ) . toBeCalledTimes ( 3 ) ;
212- expect ( data ) . toBe ( data1 ) ; // confirm that the cache persisted across renders
204+ expect ( data ) . toBe ( data0 ) ;
213205 } ) ;
214206
215207 // @gate enableUseMemoCacheHook
216208 test ( 'update component using cache with throw during render' , async ( ) => {
217- let setX ;
218209 let setN ;
219210 let shouldFail = true ;
220211 function Component ( props ) {
221- const cache = useMemoCache ( 4 ) ;
212+ const cache = useMemoCache ( 5 ) ;
222213
223214 // x is used to produce a `data` object passed to the child
224- const [ x , _setX ] = useState ( 0 ) ;
225- setX = _setX ;
226- const c_x = x !== cache [ 0 ] ;
227- cache [ 0 ] = x ;
215+ const [ x ] = useState ( 0 ) ;
216+
217+ const c_0 = x !== cache [ 0 ] ;
218+ let data ;
219+ if ( c_0 ) {
220+ data = { text : `Count ${ x } ` } ;
221+ cache [ 0 ] = x ;
222+ cache [ 1 ] = data ;
223+ } else {
224+ data = cache [ 1 ] ;
225+ }
228226
229227 // n is passed as-is to the child as a cache breaker
230228 const [ n , _setN ] = useState ( 0 ) ;
231229 setN = _setN ;
232- const c_n = n !== cache [ 1 ] ;
233- cache [ 1 ] = n ;
234230
235- // NOTE the initial failure will trigger a re-render, after which the function
236- // will early return. This validates that the runtime resets the cache on error:
237- // if it doesn't the cache will be corrupt, with the cached version of data
238- // out of data from the cached version of x.
239231 if ( n === 1 ) {
240232 if ( shouldFail ) {
241233 shouldFail = false ;
242234 throw new Error ( 'failed' ) ;
243235 }
244- setN ( 2 ) ;
245- return ;
246236 }
247237
248- let data ;
249- if ( c_x ) {
250- data = cache [ 2 ] = { text : `Count ${ x } ` } ;
238+ const c_2 = x !== cache [ 2 ] ;
239+ const c_3 = n !== cache [ 3 ] ;
240+ let t0 ;
241+ if ( c_2 || c_3 ) {
242+ t0 = < Text data = { data } n = { n } /> ;
243+ cache [ 2 ] = x ;
244+ cache [ 3 ] = n ;
245+ cache [ 4 ] = t0 ;
251246 } else {
252- data = cache [ 2 ] ;
253- }
254- if ( c_x || c_n ) {
255- return ( cache [ 3 ] = < Text data = { data } n = { n } /> ) ;
256- } else {
257- return cache [ 3 ] ;
247+ t0 = cache [ 4 ] ;
258248 }
249+ return t0 ;
259250 }
260251 let data ;
261252 const Text = jest . fn ( function Text ( props ) {
262253 data = props . data ;
263- return data . text ;
254+ return ` ${ data . text } (n= ${ props . n } )` ;
264255 } ) ;
265256
266257 spyOnDev ( console , 'error' ) ;
@@ -273,30 +264,25 @@ describe('useMemoCache()', () => {
273264 </ ErrorBoundary > ,
274265 ) ;
275266 } ) ;
276- expect ( root ) . toMatchRenderedOutput ( 'Count 0' ) ;
267+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=0) ' ) ;
277268 expect ( Text ) . toBeCalledTimes ( 1 ) ;
278269 const data0 = data ;
279270
280- // Simultaneously trigger an update to x (should create a new data value)
281- // and trigger the setState+early return. The runtime should reset the cache
282- // to avoid an inconsistency
283271 await act ( ( ) => {
284- // this update bumps the count
285- setX ( 1 ) ;
286272 // this triggers a throw.
287273 setN ( 1 ) ;
288274 } ) ;
289- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
275+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=1) ' ) ;
290276 expect ( Text ) . toBeCalledTimes ( 2 ) ;
291- expect ( data ) . not . toBe ( data0 ) ;
277+ expect ( data ) . toBe ( data0 ) ;
292278 const data1 = data ;
293279
294280 // Forcing an unrelated update shouldn't recreate the
295281 // data object.
296282 await act ( ( ) => {
297- setN ( 3 ) ;
283+ setN ( 2 ) ;
298284 } ) ;
299- expect ( root ) . toMatchRenderedOutput ( 'Count 1 ' ) ;
285+ expect ( root ) . toMatchRenderedOutput ( 'Count 0 (n=2) ' ) ;
300286 expect ( Text ) . toBeCalledTimes ( 3 ) ;
301287 expect ( data ) . toBe ( data1 ) ; // confirm that the cache persisted across renders
302288 } ) ;
0 commit comments