Skip to content

Commit 0106aab

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 0106aab

19 files changed

+1299
-30
lines changed

packages/react-reconciler/src/ReactFiberCallUserSpace.js

Lines changed: 28 additions & 6 deletions
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,29 @@ 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+
default: {
200+
if (__DEV__) {
201+
console.error(
202+
'Could not call create on an update to a ResourceEffect. This is a bug in React.',
203+
);
204+
}
205+
}
206+
}
185207
},
186208
};
187209

packages/react-reconciler/src/ReactFiberCommitEffects.js

Lines changed: 125 additions & 6 deletions
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,68 @@ export function commitHookEffectListMount(
146153

147154
// Mount
148155
let destroy;
156+
if (enableUseResourceEffectHook) {
157+
if (effect.kind === ResourceEffectIdentityKind) {
158+
if (__DEV__) {
159+
effect.resource = runWithFiberInDEV(
160+
finishedWork,
161+
callCreateInDEV,
162+
effect,
163+
);
164+
if (effect.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.resource,
169+
);
170+
}
171+
} else {
172+
effect.resource = effect.create();
173+
}
174+
if (effect.next.kind === ResourceEffectUpdateKind) {
175+
effect.next.resource = effect.resource;
176+
} else {
177+
if (__DEV__) {
178+
console.error(
179+
'Found identity effect without an update effect. This is a bug in React.',
180+
);
181+
}
182+
}
183+
destroy = effect.destroy;
184+
}
185+
if (effect.kind === ResourceEffectUpdateKind) {
186+
if (
187+
// We don't want to fire updates on remount during Activity
188+
(flags & HookHasEffect) > 0 &&
189+
typeof effect.update === 'function' &&
190+
effect.resource != null
191+
) {
192+
// TODO(@poteto) what about multiple updates?
193+
effect.update(effect.resource);
194+
}
195+
}
196+
}
149197
if (__DEV__) {
150198
if ((flags & HookInsertion) !== NoHookEffect) {
151199
setIsRunningInsertionEffect(true);
152200
}
153-
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
201+
if (effect.kind === SimpleEffectKind) {
202+
destroy = runWithFiberInDEV(
203+
finishedWork,
204+
callCreateInDEV,
205+
effect,
206+
);
207+
}
154208
if ((flags & HookInsertion) !== NoHookEffect) {
155209
setIsRunningInsertionEffect(false);
156210
}
157211
} else {
158-
const create = effect.create;
159-
const inst = effect.inst;
160-
destroy = create();
161-
inst.destroy = destroy;
212+
if (effect.kind === SimpleEffectKind) {
213+
const create = effect.create;
214+
const inst = effect.inst;
215+
destroy = create();
216+
inst.destroy = destroy;
217+
}
162218
}
163219

164220
if (enableSchedulingProfiler) {
@@ -176,6 +232,11 @@ export function commitHookEffectListMount(
176232
hookName = 'useLayoutEffect';
177233
} else if ((effect.tag & HookInsertion) !== NoFlags) {
178234
hookName = 'useInsertionEffect';
235+
} else if (
236+
enableUseResourceEffectHook &&
237+
effect.kind === ResourceEffectIdentityKind
238+
) {
239+
hookName = 'useResourceEffect';
179240
} else {
180241
hookName = 'useEffect';
181242
}
@@ -244,9 +305,34 @@ export function commitHookEffectListUnmount(
244305
if ((effect.tag & flags) === flags) {
245306
// Unmount
246307
const inst = effect.inst;
308+
if (
309+
enableUseResourceEffectHook &&
310+
effect.kind === ResourceEffectIdentityKind &&
311+
effect.resource != null
312+
) {
313+
inst.destroy = effect.destroy;
314+
}
247315
const destroy = inst.destroy;
248316
if (destroy !== undefined) {
249317
inst.destroy = undefined;
318+
let resource;
319+
if (enableUseResourceEffectHook) {
320+
if (effect.kind === ResourceEffectIdentityKind) {
321+
resource = effect.resource;
322+
effect.resource = null;
323+
// TODO(@poteto) very sketchy
324+
if (effect.next.kind === ResourceEffectUpdateKind) {
325+
effect.next.resource = null;
326+
effect.next.update = undefined;
327+
} else {
328+
if (__DEV__) {
329+
console.error(
330+
'Found identity effect without an update effect. This is a bug in React.',
331+
);
332+
}
333+
}
334+
}
335+
}
250336
if (enableSchedulingProfiler) {
251337
if ((flags & HookPassive) !== NoHookEffect) {
252338
markComponentPassiveEffectUnmountStarted(finishedWork);
@@ -260,7 +346,16 @@ export function commitHookEffectListUnmount(
260346
setIsRunningInsertionEffect(true);
261347
}
262348
}
263-
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
349+
if (enableUseResourceEffectHook) {
350+
safelyCallDestroyWithResource(
351+
finishedWork,
352+
nearestMountedAncestor,
353+
destroy,
354+
resource,
355+
);
356+
} else {
357+
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
358+
}
264359
if (__DEV__) {
265360
if ((flags & HookInsertion) !== NoHookEffect) {
266361
setIsRunningInsertionEffect(false);
@@ -895,6 +990,30 @@ function safelyCallDestroy(
895990
}
896991
}
897992

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

0 commit comments

Comments
 (0)