Skip to content

Commit 5d2ca53

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 0480cdb commit 5d2ca53

19 files changed

+1292
-31
lines changed

packages/react-reconciler/src/ReactFiberCallUserSpace.js

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

1515
import {isRendering, setIsRendering} from './ReactCurrentFiber';
1616
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
17+
import {
18+
ResourceEffectIdentityKind,
19+
ResourceEffectUpdateKind,
20+
SimpleEffectKind,
21+
} from './ReactFiberHooks';
1722

1823
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
1924
// TODO: Consider marking the whole bundle instead of these boundaries.
@@ -176,12 +181,35 @@ export const callComponentWillUnmountInDEV: (
176181
: (null: any);
177182

178183
const callCreate = {
179-
'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;
184+
'react-stack-bottom-frame': function (
185+
effect: Effect,
186+
): (() => void) | mixed | void {
187+
switch (effect.kind) {
188+
case SimpleEffectKind: {
189+
const create = effect.create;
190+
const inst = effect.inst;
191+
const destroy = create();
192+
inst.destroy = destroy;
193+
return destroy;
194+
}
195+
case ResourceEffectIdentityKind: {
196+
return effect.create();
197+
}
198+
case ResourceEffectUpdateKind: {
199+
if (typeof effect.update === 'function') {
200+
effect.update(effect.inst.resource);
201+
}
202+
break;
203+
}
204+
default: {
205+
if (__DEV__) {
206+
console.error(
207+
'Unhandled Effect kind %s. This is a bug in React.',
208+
effect.kind,
209+
);
210+
}
211+
}
212+
}
185213
},
186214
};
187215

packages/react-reconciler/src/ReactFiberCommitEffects.js

+122-7
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,63 @@ export function commitHookEffectListMount(
146153

147154
// Mount
148155
let destroy;
156+
if (enableUseResourceEffectHook) {
157+
if (effect.kind === ResourceEffectIdentityKind) {
158+
if (__DEV__) {
159+
effect.inst.resource = runWithFiberInDEV(
160+
finishedWork,
161+
callCreateInDEV,
162+
effect,
163+
);
164+
if (effect.inst.resource == null) {
165+
console.error(
166+
'useResourceEffect must provide a callback which returns a resource. ' +
167+
'If a managed resource is not needed here, use useEffect. Received %s',
168+
effect.inst.resource,
169+
);
170+
}
171+
} else {
172+
effect.inst.resource = effect.create();
173+
}
174+
destroy = effect.inst.destroy;
175+
}
176+
if (effect.kind === ResourceEffectUpdateKind) {
177+
if (
178+
// We don't want to fire updates on remount during Activity
179+
(flags & HookHasEffect) > 0 &&
180+
typeof effect.update === 'function' &&
181+
effect.inst.resource != null
182+
) {
183+
// TODO(@poteto) what about multiple updates?
184+
if (__DEV__) {
185+
runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
186+
} else {
187+
effect.update(effect.inst.resource);
188+
}
189+
}
190+
}
191+
}
149192
if (__DEV__) {
150193
if ((flags & HookInsertion) !== NoHookEffect) {
151194
setIsRunningInsertionEffect(true);
152195
}
153-
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
196+
if (effect.kind === SimpleEffectKind) {
197+
destroy = runWithFiberInDEV(
198+
finishedWork,
199+
callCreateInDEV,
200+
effect,
201+
);
202+
}
154203
if ((flags & HookInsertion) !== NoHookEffect) {
155204
setIsRunningInsertionEffect(false);
156205
}
157206
} else {
158-
const create = effect.create;
159-
const inst = effect.inst;
160-
destroy = create();
161-
inst.destroy = destroy;
207+
if (effect.kind === SimpleEffectKind) {
208+
const create = effect.create;
209+
const inst = effect.inst;
210+
destroy = create();
211+
inst.destroy = destroy;
212+
}
162213
}
163214

164215
if (enableSchedulingProfiler) {
@@ -176,6 +227,11 @@ export function commitHookEffectListMount(
176227
hookName = 'useLayoutEffect';
177228
} else if ((effect.tag & HookInsertion) !== NoFlags) {
178229
hookName = 'useInsertionEffect';
230+
} else if (
231+
enableUseResourceEffectHook &&
232+
effect.kind === ResourceEffectIdentityKind
233+
) {
234+
hookName = 'useResourceEffect';
179235
} else {
180236
hookName = 'useEffect';
181237
}
@@ -246,7 +302,9 @@ export function commitHookEffectListUnmount(
246302
const inst = effect.inst;
247303
const destroy = inst.destroy;
248304
if (destroy !== undefined) {
249-
inst.destroy = undefined;
305+
if (effect.kind === SimpleEffectKind) {
306+
inst.destroy = undefined;
307+
}
250308
if (enableSchedulingProfiler) {
251309
if ((flags & HookPassive) !== NoHookEffect) {
252310
markComponentPassiveEffectUnmountStarted(finishedWork);
@@ -260,7 +318,40 @@ export function commitHookEffectListUnmount(
260318
setIsRunningInsertionEffect(true);
261319
}
262320
}
263-
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
321+
if (enableUseResourceEffectHook) {
322+
if (
323+
effect.kind === ResourceEffectIdentityKind &&
324+
effect.inst.resource != null
325+
) {
326+
safelyCallDestroyWithResource(
327+
finishedWork,
328+
nearestMountedAncestor,
329+
destroy,
330+
effect.inst.resource,
331+
);
332+
if (effect.next.kind === ResourceEffectUpdateKind) {
333+
effect.next.update = undefined;
334+
} else {
335+
if (__DEV__) {
336+
console.error(
337+
'Expected a ResourceEffectUpdateKind to follow ResourceEffectIdentityKind, ' +
338+
'got %s. This is a bug in React.',
339+
effect.next.kind,
340+
);
341+
}
342+
}
343+
effect.inst.resource = null;
344+
}
345+
if (effect.kind === SimpleEffectKind) {
346+
safelyCallDestroy(
347+
finishedWork,
348+
nearestMountedAncestor,
349+
destroy,
350+
);
351+
}
352+
} else {
353+
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
354+
}
264355
if (__DEV__) {
265356
if ((flags & HookInsertion) !== NoHookEffect) {
266357
setIsRunningInsertionEffect(false);
@@ -895,6 +986,30 @@ function safelyCallDestroy(
895986
}
896987
}
897988

989+
function safelyCallDestroyWithResource(
990+
current: Fiber,
991+
nearestMountedAncestor: Fiber | null,
992+
destroy: mixed => void,
993+
resource: mixed,
994+
) {
995+
const destroy_ = resource == null ? destroy : destroy.bind(null, resource);
996+
if (__DEV__) {
997+
runWithFiberInDEV(
998+
current,
999+
callDestroyInDEV,
1000+
current,
1001+
nearestMountedAncestor,
1002+
destroy_,
1003+
);
1004+
} else {
1005+
try {
1006+
destroy_();
1007+
} catch (error) {
1008+
captureCommitPhaseError(current, nearestMountedAncestor, error);
1009+
}
1010+
}
1011+
}
1012+
8981013
function commitProfiler(
8991014
finishedWork: Fiber,
9001015
current: Fiber | null,

0 commit comments

Comments
 (0)