Skip to content

Commit f3f0919

Browse files
committed
[crud] Basic implementation
This PR introduces a new experimental hook `useResourceEffect`, which is something that we're doing some very early initial tests on. This may likely not pan out and will be removed or modified if so. Please do not rely on it as it will break.
1 parent 3770c11 commit f3f0919

18 files changed

+1260
-30
lines changed

packages/react-reconciler/src/ReactFiberCallUserSpace.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {CapturedValue} from './ReactCapturedValue';
1414

1515
import {isRendering, setIsRendering} from './ReactCurrentFiber';
1616
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
17+
import {SimpleEffectKind} from './ReactFiberHooks';
1718

1819
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
1920
// TODO: Consider marking the whole bundle instead of these boundaries.
@@ -177,11 +178,13 @@ export const callComponentWillUnmountInDEV: (
177178

178179
const callCreate = {
179180
'react-stack-bottom-frame': function (effect: Effect): (() => void) | void {
180-
const create = effect.create;
181-
const inst = effect.inst;
182-
const destroy = create();
183-
inst.destroy = destroy;
184-
return destroy;
181+
if (effect.kind === SimpleEffectKind) {
182+
const create = effect.create;
183+
const inst = effect.inst;
184+
const destroy = create();
185+
inst.destroy = destroy;
186+
return destroy;
187+
}
185188
},
186189
};
187190

packages/react-reconciler/src/ReactFiberCommitEffects.js

+107-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
enableProfilerNestedUpdatePhase,
1919
enableSchedulingProfiler,
2020
enableScopeAPI,
21+
enableUseResourceEffectHook,
2122
} from 'shared/ReactFeatureFlags';
2223
import {
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';
5355
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
5456
import {
@@ -70,6 +72,11 @@ import {
7072
} from './ReactFiberCallUserSpace';
7173

7274
import {runWithFiberInDEV} from './ReactCurrentFiber';
75+
import {
76+
ResourceEffectIdentityKind,
77+
ResourceEffectUpdateKind,
78+
SimpleEffectKind,
79+
} from './ReactFiberHooks';
7380

7481
function shouldProfile(current: Fiber): boolean {
7582
return (
@@ -146,19 +153,56 @@ export function commitHookEffectListMount(
146153

147154
// Mount
148155
let destroy;
156+
if (enableUseResourceEffectHook) {
157+
if (effect.kind === ResourceEffectIdentityKind) {
158+
effect.resource = effect.create();
159+
if (__DEV__) {
160+
if (effect.resource == null) {
161+
console.error(
162+
'useResourceEffect must provide a callback which returns a resource. ' +
163+
'If a managed resource is not needed here, use useEffect. Received %s',
164+
effect.resource,
165+
);
166+
}
167+
}
168+
if (effect.next.kind === ResourceEffectUpdateKind) {
169+
effect.next.resource = effect.resource;
170+
}
171+
destroy = effect.destroy;
172+
}
173+
if (effect.kind === ResourceEffectUpdateKind) {
174+
if (
175+
// We don't want to fire updates on remount during Activity
176+
(flags & HookHasEffect) > 0 &&
177+
typeof effect.update === 'function' &&
178+
effect.resource != null
179+
) {
180+
// TODO(@poteto) what about multiple updates?
181+
effect.update(effect.resource);
182+
}
183+
}
184+
}
149185
if (__DEV__) {
150186
if ((flags & HookInsertion) !== NoHookEffect) {
151187
setIsRunningInsertionEffect(true);
152188
}
153-
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
189+
if (effect.kind === SimpleEffectKind) {
190+
destroy = runWithFiberInDEV(
191+
finishedWork,
192+
callCreateInDEV,
193+
effect,
194+
);
195+
}
154196
if ((flags & HookInsertion) !== NoHookEffect) {
155197
setIsRunningInsertionEffect(false);
156198
}
157199
} else {
158-
const create = effect.create;
159-
const inst = effect.inst;
160-
destroy = create();
161-
inst.destroy = destroy;
200+
if (effect.kind === SimpleEffectKind) {
201+
const create = effect.create;
202+
const inst = effect.inst;
203+
destroy = create();
204+
inst.destroy = destroy;
205+
}
162206
}
163207

164208
if (enableSchedulingProfiler) {
@@ -176,6 +220,11 @@ export function commitHookEffectListMount(
176220
hookName = 'useLayoutEffect';
177221
} else if ((effect.tag & HookInsertion) !== NoFlags) {
178222
hookName = 'useInsertionEffect';
223+
} else if (
224+
enableUseResourceEffectHook &&
225+
effect.kind === ResourceEffectIdentityKind
226+
) {
227+
hookName = 'useResourceEffect';
179228
} else {
180229
hookName = 'useEffect';
181230
}
@@ -244,9 +293,28 @@ export function commitHookEffectListUnmount(
244293
if ((effect.tag & flags) === flags) {
245294
// Unmount
246295
const inst = effect.inst;
296+
if (
297+
enableUseResourceEffectHook &&
298+
effect.kind === ResourceEffectIdentityKind &&
299+
effect.resource != null
300+
) {
301+
inst.destroy = effect.destroy;
302+
}
247303
const destroy = inst.destroy;
248304
if (destroy !== undefined) {
249305
inst.destroy = undefined;
306+
let resource;
307+
if (enableUseResourceEffectHook) {
308+
if (effect.kind === ResourceEffectIdentityKind) {
309+
resource = effect.resource;
310+
effect.resource = null;
311+
// TODO(@poteto) very sketchy
312+
if (effect.next.kind === ResourceEffectUpdateKind) {
313+
effect.next.resource = null;
314+
effect.next.update = undefined;
315+
}
316+
}
317+
}
250318
if (enableSchedulingProfiler) {
251319
if ((flags & HookPassive) !== NoHookEffect) {
252320
markComponentPassiveEffectUnmountStarted(finishedWork);
@@ -260,7 +328,16 @@ export function commitHookEffectListUnmount(
260328
setIsRunningInsertionEffect(true);
261329
}
262330
}
263-
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
331+
if (enableUseResourceEffectHook) {
332+
safelyCallDestroyWithResource(
333+
finishedWork,
334+
nearestMountedAncestor,
335+
destroy,
336+
resource,
337+
);
338+
} else {
339+
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
340+
}
264341
if (__DEV__) {
265342
if ((flags & HookInsertion) !== NoHookEffect) {
266343
setIsRunningInsertionEffect(false);
@@ -895,6 +972,30 @@ function safelyCallDestroy(
895972
}
896973
}
897974

975+
function safelyCallDestroyWithResource(
976+
current: Fiber,
977+
nearestMountedAncestor: Fiber | null,
978+
destroy: mixed => void,
979+
resource: mixed,
980+
) {
981+
const destroy_ = resource == null ? destroy : destroy.bind(null, resource);
982+
if (__DEV__) {
983+
runWithFiberInDEV(
984+
current,
985+
callDestroyInDEV,
986+
current,
987+
nearestMountedAncestor,
988+
destroy_,
989+
);
990+
} else {
991+
try {
992+
destroy_();
993+
} catch (error) {
994+
captureCommitPhaseError(current, nearestMountedAncestor, error);
995+
}
996+
}
997+
}
998+
898999
function commitProfiler(
8991000
finishedWork: Fiber,
9001001
current: Fiber | null,

0 commit comments

Comments
 (0)