@@ -18,6 +18,7 @@ import {
1818 enableProfilerNestedUpdatePhase ,
1919 enableSchedulingProfiler ,
2020 enableScopeAPI ,
21+ enableUseResourceEffectHook ,
2122} from 'shared/ReactFeatureFlags' ;
2223import {
2324 ClassComponent ,
@@ -49,6 +50,7 @@ import {
4950 Layout as HookLayout ,
5051 Insertion as HookInsertion ,
5152 Passive as HookPassive ,
53+ HasEffect as HookHasEffect ,
5254} from './ReactHookEffectTags' ;
5355import { didWarnAboutReassigningProps } from './ReactFiberBeginWork' ;
5456import {
@@ -70,6 +72,10 @@ import {
7072} from './ReactFiberCallUserSpace' ;
7173
7274import { runWithFiberInDEV } from './ReactCurrentFiber' ;
75+ import {
76+ ResourceEffectIdentityKind ,
77+ ResourceEffectUpdateKind ,
78+ } from './ReactFiberHooks' ;
7379
7480function shouldProfile ( current : Fiber ) : boolean {
7581 return (
@@ -146,19 +152,90 @@ export function commitHookEffectListMount(
146152
147153 // Mount
148154 let destroy ;
155+ if ( enableUseResourceEffectHook ) {
156+ if ( effect . resourceKind === ResourceEffectIdentityKind ) {
157+ if ( __DEV__ ) {
158+ effect . inst . resource = runWithFiberInDEV (
159+ finishedWork ,
160+ callCreateInDEV ,
161+ effect ,
162+ ) ;
163+ if ( effect . inst . resource == null ) {
164+ console . error (
165+ 'useResourceEffect must provide a callback which returns a resource. ' +
166+ 'If a managed resource is not needed here, use useEffect. Received %s' ,
167+ effect . inst . resource ,
168+ ) ;
169+ }
170+ } else {
171+ effect . inst . resource = effect . create ( ) ;
172+ }
173+ destroy = effect . inst . destroy ;
174+ }
175+ if ( effect . resourceKind === ResourceEffectUpdateKind ) {
176+ if (
177+ // We don't want to fire updates on remount during Activity
178+ ( flags & HookHasEffect ) > 0 &&
179+ typeof effect . update === 'function' &&
180+ effect . inst . resource != null
181+ ) {
182+ // TODO(@poteto) what about multiple updates?
183+ if ( __DEV__ ) {
184+ runWithFiberInDEV ( finishedWork , callCreateInDEV , effect ) ;
185+ } else {
186+ effect . update ( effect . inst . resource ) ;
187+ }
188+ }
189+ }
190+ }
149191 if ( __DEV__ ) {
150192 if ( ( flags & HookInsertion ) !== NoHookEffect ) {
151193 setIsRunningInsertionEffect ( true ) ;
152194 }
153- destroy = runWithFiberInDEV ( finishedWork , callCreateInDEV , effect ) ;
195+ if ( enableUseResourceEffectHook ) {
196+ if ( effect . resourceKind == null ) {
197+ destroy = runWithFiberInDEV (
198+ finishedWork ,
199+ callCreateInDEV ,
200+ effect ,
201+ ) ;
202+ }
203+ } else {
204+ destroy = runWithFiberInDEV (
205+ finishedWork ,
206+ callCreateInDEV ,
207+ effect ,
208+ ) ;
209+ }
154210 if ( ( flags & HookInsertion ) !== NoHookEffect ) {
155211 setIsRunningInsertionEffect ( false ) ;
156212 }
157213 } else {
158- const create = effect . create ;
159- const inst = effect . inst ;
160- destroy = create ( ) ;
161- inst . destroy = destroy ;
214+ if ( enableUseResourceEffectHook ) {
215+ if ( effect . resourceKind == null ) {
216+ const create = effect . create ;
217+ const inst = effect . inst ;
218+ destroy = create ( ) ;
219+ inst . destroy = destroy ;
220+ }
221+ } else {
222+ if ( effect . resourceKind != null ) {
223+ if ( __DEV__ ) {
224+ console . error (
225+ 'Expected only SimpleEffects when enableUseResourceEffectHook is disabled, ' +
226+ 'got %s' ,
227+ effect . resourceKind ,
228+ ) ;
229+ }
230+ }
231+ const create = effect . create ;
232+ const inst = effect . inst ;
233+ // $FlowFixMe[incompatible-type] (@poteto)
234+ // $FlowFixMe[not-a-function] (@poteto)
235+ destroy = create ( ) ;
236+ // $FlowFixMe[incompatible-type] (@poteto)
237+ inst . destroy = destroy ;
238+ }
162239 }
163240
164241 if ( enableSchedulingProfiler ) {
@@ -176,6 +253,11 @@ export function commitHookEffectListMount(
176253 hookName = 'useLayoutEffect' ;
177254 } else if ( ( effect . tag & HookInsertion ) !== NoFlags ) {
178255 hookName = 'useInsertionEffect' ;
256+ } else if (
257+ enableUseResourceEffectHook &&
258+ effect . resourceKind != null
259+ ) {
260+ hookName = 'useResourceEffect' ;
179261 } else {
180262 hookName = 'useEffect' ;
181263 }
@@ -202,6 +284,7 @@ export function commitHookEffectListMount(
202284 `}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
203285 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching' ;
204286 } else {
287+ // $FlowFixMe[unsafe-addition] (@poteto)
205288 addendum = ' You returned: ' + destroy ;
206289 }
207290 runWithFiberInDEV (
@@ -246,7 +329,13 @@ export function commitHookEffectListUnmount(
246329 const inst = effect . inst ;
247330 const destroy = inst . destroy ;
248331 if ( destroy !== undefined ) {
249- inst . destroy = undefined ;
332+ if ( enableUseResourceEffectHook ) {
333+ if ( effect . resourceKind == null ) {
334+ inst . destroy = undefined ;
335+ }
336+ } else {
337+ inst . destroy = undefined ;
338+ }
250339 if ( enableSchedulingProfiler ) {
251340 if ( ( flags & HookPassive ) !== NoHookEffect ) {
252341 markComponentPassiveEffectUnmountStarted ( finishedWork ) ;
@@ -260,7 +349,41 @@ export function commitHookEffectListUnmount(
260349 setIsRunningInsertionEffect ( true ) ;
261350 }
262351 }
263- safelyCallDestroy ( finishedWork , nearestMountedAncestor , destroy ) ;
352+ if ( enableUseResourceEffectHook ) {
353+ if (
354+ effect . resourceKind === ResourceEffectIdentityKind &&
355+ effect . inst . resource != null
356+ ) {
357+ safelyCallDestroyWithResource (
358+ finishedWork ,
359+ nearestMountedAncestor ,
360+ destroy ,
361+ effect . inst . resource ,
362+ ) ;
363+ if ( effect . next . resourceKind === ResourceEffectUpdateKind ) {
364+ // $FlowFixMe[prop-missing] (@poteto)
365+ effect . next . update = undefined ;
366+ } else {
367+ if ( __DEV__ ) {
368+ console . error (
369+ 'Expected a ResourceEffectUpdateKind to follow ResourceEffectIdentityKind, ' +
370+ 'got %s. This is a bug in React.' ,
371+ effect . next . resourceKind ,
372+ ) ;
373+ }
374+ }
375+ effect . inst . resource = null ;
376+ }
377+ if ( effect . resourceKind == null ) {
378+ safelyCallDestroy (
379+ finishedWork ,
380+ nearestMountedAncestor ,
381+ destroy ,
382+ ) ;
383+ }
384+ } else {
385+ safelyCallDestroy ( finishedWork , nearestMountedAncestor , destroy ) ;
386+ }
264387 if ( __DEV__ ) {
265388 if ( ( flags & HookInsertion ) !== NoHookEffect ) {
266389 setIsRunningInsertionEffect ( false ) ;
@@ -895,6 +1018,30 @@ function safelyCallDestroy(
8951018 }
8961019}
8971020
1021+ function safelyCallDestroyWithResource (
1022+ current : Fiber ,
1023+ nearestMountedAncestor : Fiber | null ,
1024+ destroy : mixed => void ,
1025+ resource : mixed ,
1026+ ) {
1027+ const destroy_ = resource == null ? destroy : destroy . bind ( null , resource ) ;
1028+ if ( __DEV__ ) {
1029+ runWithFiberInDEV (
1030+ current ,
1031+ callDestroyInDEV ,
1032+ current ,
1033+ nearestMountedAncestor ,
1034+ destroy_ ,
1035+ ) ;
1036+ } else {
1037+ try {
1038+ destroy_ ( ) ;
1039+ } catch ( error ) {
1040+ captureCommitPhaseError ( current , nearestMountedAncestor , error ) ;
1041+ }
1042+ }
1043+ }
1044+
8981045function commitProfiler(
8991046 finishedWork: Fiber,
9001047 current: Fiber | null,
0 commit comments