[crud] Basic implementation

wip:

- [ ] more tests
- [ ] error handling
- [ ] flow shenanigans
This commit is contained in:
lauren
2024-11-05 14:45:35 -05:00
parent b81e6dd2da
commit bfdbbd27d0
19 changed files with 630 additions and 24 deletions
+17 -5
View File
@@ -14,6 +14,7 @@ import type {CapturedValue} from './ReactCapturedValue';
import {isRendering, setIsRendering} from './ReactCurrentFiber';
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
import {SimpleEffectKind} from './ReactFiberHooks';
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
// TODO: Consider marking the whole bundle instead of these boundaries.
@@ -177,11 +178,22 @@ export const callComponentWillUnmountInDEV: (
const callCreate = {
'react-stack-bottom-frame': function (effect: Effect): (() => void) | void {
const create = effect.create;
const inst = effect.inst;
const destroy = create();
inst.destroy = destroy;
return destroy;
if (effect.kind === SimpleEffectKind) {
const create = effect.create;
const inst = effect.inst;
const destroy = create();
inst.destroy = destroy;
return destroy;
} else if (typeof effect.destroy === 'function') {
const inst = effect.inst;
const _destroy = effect.destroy;
const destroy = () => {
_destroy(effect.resource);
effect.resource = null;
};
inst.destroy = destroy;
return destroy;
}
},
};
+41 -5
View File
@@ -71,6 +71,7 @@ import {
} from './ReactFiberCallUserSpace';
import {runWithFiberInDEV} from './ReactCurrentFiber';
import {ResourceEffectKind} from './ReactFiberHooks';
function shouldProfile(current: Fiber): boolean {
return (
@@ -151,15 +152,37 @@ export function commitHookEffectListMount(
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(true);
}
destroy = runWithFiberInDEV(finishedWork, callCreateInDEV, effect);
if (effect.kind === ResourceEffectKind) {
if (typeof effect.create === 'function') {
effect.resource = effect.create();
} else if (typeof effect.update === 'function') {
// TODO: what about multiple updates?
effect.update(effect.resource);
}
} else {
destroy = runWithFiberInDEV(
finishedWork,
callCreateInDEV,
effect,
);
}
if ((flags & HookInsertion) !== NoHookEffect) {
setIsRunningInsertionEffect(false);
}
} else {
const create = effect.create;
const inst = effect.inst;
destroy = create();
inst.destroy = destroy;
if (effect.kind === ResourceEffectKind) {
if (typeof effect.create === 'function') {
effect.resource = effect.create();
} else if (typeof effect.update === 'function') {
// TODO: what about multiple updates?
effect.update(effect.resource);
}
} else {
const create = effect.create;
const inst = effect.inst;
destroy = create();
inst.destroy = destroy;
}
}
if (enableSchedulingProfiler) {
@@ -243,6 +266,19 @@ export function commitHookEffectListUnmount(
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
if (effect.kind === ResourceEffectKind) {
if (
effect.resource != null &&
typeof effect.destroy === 'function'
) {
effect.inst.destroy = function () {
// $FlowFixMe trust me bro
effect.destroy(effect.resource);
effect.resource = null;
};
}
}
// Unmount
const inst = effect.inst;
const destroy = inst.destroy;
+358 -14
View File
@@ -48,6 +48,7 @@ import {
disableLegacyMode,
enableNoCloningMemoCache,
enableContextProfiling,
enableUseResourceEffectHook,
} from 'shared/ReactFeatureFlags';
import {
REACT_CONTEXT_TYPE,
@@ -218,12 +219,33 @@ type EffectInstance = {
destroy: void | (() => void),
};
export type Effect = {
export const SimpleEffectKind: 0 = 0;
export const ResourceEffectKind: 1 = 1;
export type EffectKind = typeof SimpleEffectKind | typeof ResourceEffectKind;
export type Effect = SimpleEffect | ResourceEffect;
export type SimpleEffect = {
kind: typeof SimpleEffectKind,
tag: HookFlags,
create: () => (() => void) | void,
inst: EffectInstance,
deps: Array<mixed> | null,
create: () => (() => void) | void,
createDeps: Array<mixed> | null,
update: void | null,
updateDeps: void | null,
destroy: void | null,
next: Effect,
resource: mixed,
};
export type ResourceEffect = {
kind: typeof ResourceEffectKind,
tag: HookFlags,
create: () => {},
inst: EffectInstance,
createDeps: Array<mixed> | void | null,
update: ((resource: mixed) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: ((resource: mixed) => void) | void,
next: Effect,
resource: mixed,
};
type StoreInstance<T> = {
@@ -1720,8 +1742,9 @@ function mountSyncExternalStore<T>(
fiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
createEffectInstance(),
SimpleEffectKind,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
null,
);
@@ -1790,9 +1813,10 @@ function updateSyncExternalStore<T>(
fiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
createEffectInstance(),
null,
SimpleEffectKind,
updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot),
undefined,
);
// Unless we're rendering a blocking lane, schedule a consistency check.
@@ -2450,8 +2474,9 @@ function updateActionStateImpl<S, P>(
currentlyRenderingFiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
actionStateActionEffect.bind(null, actionQueue, action),
createEffectInstance(),
SimpleEffectKind,
actionStateActionEffect.bind(null, actionQueue, action),
null,
);
}
@@ -2510,15 +2535,26 @@ function rerenderActionState<S, P>(
function pushEffect(
tag: HookFlags,
create: () => (() => void) | void,
inst: EffectInstance,
deps: Array<mixed> | null,
kind: EffectKind,
create: () => (() => void) | {} | void,
createDeps: Array<mixed> | void | null,
update: (({}) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (({}) => void) | void,
resource: mixed,
): Effect {
// $FlowFixMe jordan help pls
const effect: Effect = {
kind,
tag,
create,
createDeps,
update,
updateDeps,
destroy,
inst,
deps,
resource,
// Circular
next: (null: any),
};
@@ -2567,8 +2603,9 @@ function mountEffectImpl(
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
createEffectInstance(),
SimpleEffectKind,
create,
nextDeps,
);
}
@@ -2589,9 +2626,16 @@ function updateEffectImpl(
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
const prevDeps = prevEffect.createDeps;
// $FlowFixMe lauren
if (areHookInputsEqual(nextDeps, prevDeps)) {
hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
hook.memoizedState = pushEffect(
hookFlags,
inst,
SimpleEffectKind,
create,
nextDeps,
);
return;
}
}
@@ -2601,8 +2645,9 @@ function updateEffectImpl(
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
inst,
SimpleEffectKind,
create,
nextDeps,
);
}
@@ -2639,6 +2684,148 @@ function updateEffect(
updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function mountResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (({}) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
) {
mountResourceEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
createDeps,
update,
updateDeps,
destroy,
);
} else {
mountResourceEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
createDeps,
update,
updateDeps,
destroy,
);
}
}
function mountResourceEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (({}) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
const hook = mountWorkInProgressHook();
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
createEffectInstance(),
ResourceEffectKind,
create,
createDeps,
update,
updateDeps,
destroy,
);
}
function updateResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (({}) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
updateResourceEffectImpl(
PassiveEffect,
HookPassive,
create,
createDeps,
update,
updateDeps,
destroy,
);
}
function updateResourceEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (({}) => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
const hook = updateWorkInProgressHook();
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
const nextCreateDepsArray = createDeps != null ? createDeps : [];
const nextUpdateDeps = updateDeps !== undefined ? updateDeps : null;
let isCreateDepsSame: boolean;
if (currentHook !== null) {
const prevEffect: Effect = currentHook.memoizedState;
const prevCreateDepsArray =
prevEffect.createDeps != null ? prevEffect.createDeps : [];
isCreateDepsSame = areHookInputsEqual(
nextCreateDepsArray,
prevCreateDepsArray,
);
if (nextUpdateDeps !== null) {
const prevUpdateDeps =
prevEffect.updateDeps != null ? prevEffect.updateDeps : null;
if (
isCreateDepsSame &&
areHookInputsEqual(nextUpdateDeps, prevUpdateDeps)
) {
hook.memoizedState = pushEffect(
hookFlags,
inst,
ResourceEffectKind,
create,
nextUpdateDeps,
update,
updateDeps,
destroy,
);
hook.memoizedState.resource = prevEffect.resource;
return;
}
}
}
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
inst,
ResourceEffectKind,
// $FlowFixMe lauren
isCreateDepsSame ? undefined : create,
nextCreateDepsArray,
update,
nextUpdateDeps,
isCreateDepsSame ? undefined : destroy,
);
if (currentHook != null) {
const currentHookState: Effect = currentHook.memoizedState;
hook.memoizedState.resource = currentHookState.resource;
}
}
function useEffectEventImpl<Args, Return, F: (...Array<Args>) => Return>(
payload: EventFunctionPayload<Args, Return, F>,
) {
@@ -3789,6 +3976,9 @@ if (enableUseMemoCacheHook) {
if (enableUseEffectEventHook) {
(ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
}
if (enableUseResourceEffectHook) {
(ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError;
}
if (enableAsyncActions) {
(ContextOnlyDispatcher: Dispatcher).useHostTransitionStatus =
throwInvalidHookError;
@@ -3832,6 +4022,9 @@ if (enableUseMemoCacheHook) {
if (enableUseEffectEventHook) {
(HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect;
}
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -3875,6 +4068,10 @@ if (enableUseMemoCacheHook) {
if (enableUseEffectEventHook) {
(HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnUpdate: Dispatcher).useResourceEffect =
updateResourceEffect;
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -3918,6 +4115,10 @@ if (enableUseMemoCacheHook) {
if (enableUseEffectEventHook) {
(HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnRerender: Dispatcher).useResourceEffect =
updateResourceEffect;
}
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -4108,6 +4309,26 @@ if (__DEV__) {
return mountEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
): void {
currentHookNameInDev = 'useResourceEffect';
mountHookTypesDev();
return mountResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -4300,6 +4521,26 @@ if (__DEV__) {
return mountEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
): void {
currentHookNameInDev = 'useResourceEffect';
updateHookTypesDev();
return mountResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -4491,6 +4732,26 @@ if (__DEV__) {
return updateEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
currentHookNameInDev = 'useResourceEffect';
updateHookTypesDev();
return updateResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -4682,6 +4943,26 @@ if (__DEV__) {
return updateEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
currentHookNameInDev = 'useResourceEffect';
updateHookTypesDev();
return updateResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -4897,6 +5178,27 @@ if (__DEV__) {
return mountEvent(callback);
};
}
if (InvalidNestedHooksDispatcherOnMountInDEV) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
): void {
currentHookNameInDev = 'useResourceEffect';
warnInvalidHookAccess();
mountHookTypesDev();
return mountResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -5115,6 +5417,27 @@ if (__DEV__) {
return updateEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
currentHookNameInDev = 'useResourceEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
@@ -5333,6 +5656,27 @@ if (__DEV__) {
return updateEvent(callback);
};
}
if (enableUseResourceEffectHook) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect =
function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) {
currentHookNameInDev = 'useResourceEffect';
warnInvalidHookAccess();
updateHookTypesDev();
return updateResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
};
}
if (enableAsyncActions) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
+8
View File
@@ -47,6 +47,7 @@ export type HookType =
| 'useRef'
| 'useEffect'
| 'useEffectEvent'
| 'useResourceEffect'
| 'useInsertionEffect'
| 'useLayoutEffect'
| 'useCallback'
@@ -412,6 +413,13 @@ export type Dispatcher = {
deps: Array<mixed> | void | null,
): void,
useEffectEvent?: <Args, F: (...Array<Args>) => mixed>(callback: F) => F,
useResourceEffect?: (
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
) => void,
useInsertionEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
@@ -41,6 +41,7 @@ let waitFor;
let waitForThrow;
let waitForPaint;
let assertLog;
let useResourceEffect;
describe('ReactHooksWithNoopRenderer', () => {
beforeEach(() => {
@@ -66,6 +67,7 @@ describe('ReactHooksWithNoopRenderer', () => {
useDeferredValue = React.useDeferredValue;
Suspense = React.Suspense;
Activity = React.unstable_Activity;
useResourceEffect = React.experimental_useResourceEffect;
ContinuousEventPriority =
require('react-reconciler/constants').ContinuousEventPriority;
if (gate(flags => flags.enableSuspenseList)) {
@@ -3252,6 +3254,171 @@ describe('ReactHooksWithNoopRenderer', () => {
});
});
// @gate enableUseResourceEffectHook
describe('useResourceEffect', () => {
class Resource {
id: string;
opts: mixed;
constructor(id, opts) {
this.id = id;
this.opts = opts;
Scheduler.log(`create(${this.id}, ${this.opts.username})`);
}
update(opts) {
this.opts = opts;
Scheduler.log(`update(${this.id}, ${this.opts.username})`);
}
destroy() {
Scheduler.log(`destroy(${this.id}, ${this.opts.username})`);
}
}
// @gate enableUseResourceEffectHook
it('simple mount and update', async () => {
const root = ReactNoop.createRoot();
function App({id, username}) {
const opts = useMemo(() => {
return {username};
}, [username]);
useResourceEffect(
() => new Resource(id, opts),
[id],
resource => resource.update(opts),
[opts],
resource => resource.destroy(),
);
return null;
}
await act(() => {
root.render(<App id={1} username="Jack" />);
});
assertLog(['create(1, Jack)']);
await act(() => {
root.render(<App id={1} username="Lauren" />);
});
assertLog(['update(1, Lauren)']);
await act(() => {
root.render(<App id={1} username="Jordan" />);
});
assertLog(['update(1, Jordan)']);
await act(() => {
root.render(<App id={2} username="Jack" />);
});
assertLog(['destroy(1, Jordan)', 'create(2, Jack)']);
await act(() => {
root.render(null);
});
assertLog(['destroy(2, Jack)']);
});
// @gate enableUseResourceEffectHook
it('simple mount with no update', async () => {
const root = ReactNoop.createRoot();
function App({id, username}) {
const opts = useMemo(() => {
return {username};
}, [username]);
useResourceEffect(
() => new Resource(id, opts),
[id],
resource => resource.update(opts),
[opts],
resource => resource.destroy(),
);
return null;
}
await act(() => {
root.render(<App id={1} username="Jack" />);
});
assertLog(['create(1, Jack)']);
await act(() => {
root.render(null);
});
assertLog(['destroy(1, Jack)']);
});
// @gate enableUseResourceEffectHook
it('calls update on every render if no deps are specified', async () => {
const root = ReactNoop.createRoot();
function App({id, username}) {
const opts = useMemo(() => {
return {username};
}, [username]);
useResourceEffect(
() => new Resource(id, opts),
[id],
resource => resource.update(opts),
);
return null;
}
await act(() => {
root.render(<App id={1} username="Jack" />);
});
assertLog(['create(1, Jack)']);
await act(() => {
root.render(<App id={1} username="Jack" />);
});
assertLog(['update(1, Jack)']);
await act(() => {
root.render(<App id={2} username="Jack" />);
});
assertLog(['create(2, Jack)']);
await act(() => {
root.render(<App id={2} username="Lauren" />);
});
assertLog(['update(2, Lauren)']);
});
// @gate enableUseResourceEffectHook
it('calls useResourceEffect methods only on mount/unmount', async () => {
const root = ReactNoop.createRoot();
function App({id, username}) {
const opts = useMemo(() => {
return {username};
}, [username]);
useResourceEffect(
() => new Resource(id, opts),
[],
resource => resource.update(opts),
[],
resource => resource.destroy(),
);
return null;
}
await act(() => {
root.render(<App id={1} username="Jack" />);
});
assertLog(['create(1, Jack)']);
await act(() => {
root.render(<App id={2} username="Joe" />);
});
assertLog([]);
await act(() => {
root.render(null);
});
assertLog(['destroy(1, Jack)']);
});
});
describe('useCallback', () => {
it('memoizes callback by comparing inputs', async () => {
class IncrementButton extends React.PureComponent {
+1
View File
@@ -60,6 +60,7 @@ export {
useDeferredValue,
useEffect,
experimental_useEffectEvent,
experimental_useResourceEffect,
useImperativeHandle,
useInsertionEffect,
useLayoutEffect,
@@ -41,6 +41,7 @@ export {
useDeferredValue,
useEffect,
experimental_useEffectEvent,
experimental_useResourceEffect,
useImperativeHandle,
useInsertionEffect,
useLayoutEffect,
+1
View File
@@ -41,6 +41,7 @@ export {
useDeferredValue,
useEffect,
experimental_useEffectEvent,
experimental_useResourceEffect,
useImperativeHandle,
useInsertionEffect,
useLayoutEffect,
+1
View File
@@ -19,6 +19,7 @@ export {
createElement,
createRef,
experimental_useEffectEvent,
experimental_useResourceEffect,
forwardRef,
Fragment,
isValidElement,
+1
View File
@@ -61,6 +61,7 @@ export {
useDeferredValue,
useEffect,
experimental_useEffectEvent,
experimental_useResourceEffect,
useImperativeHandle,
useInsertionEffect,
useLayoutEffect,
+2
View File
@@ -42,6 +42,7 @@ import {
useContext,
useEffect,
useEffectEvent,
useResourceEffect,
useImperativeHandle,
useDebugValue,
useInsertionEffect,
@@ -89,6 +90,7 @@ export {
useContext,
useEffect,
useEffectEvent as experimental_useEffectEvent,
useResourceEffect as experimental_useResourceEffect,
useImperativeHandle,
useDebugValue,
useInsertionEffect,
+18
View File
@@ -226,6 +226,24 @@ export function useEffectEvent<Args, F: (...Array<Args>) => mixed>(
return dispatcher.useEffectEvent(callback);
}
export function useResourceEffect(
create: () => {} | void,
createDeps: Array<mixed> | void | null,
update: (() => void) | void,
updateDeps: Array<mixed> | void | null,
destroy: (() => void) | void,
): void {
const dispatcher = resolveDispatcher();
// $FlowFixMe[not-a-function] This is unstable, thus optional
return dispatcher.useResourceEffect(
create,
createDeps,
update,
updateDeps,
destroy,
);
}
export function useOptimistic<S, A>(
passthrough: S,
reducer: ?(S, A) => S,
+5
View File
@@ -161,6 +161,11 @@ export const transitionLaneExpirationMs = 5000;
*/
export const enableInfiniteRenderLoopDetection = false;
/**
* Experimental new hook for better managing resources in effects.
*/
export const enableUseResourceEffectHook = __EXPERIMENTAL__;
// -----------------------------------------------------------------------------
// Ready for next major.
//
@@ -94,6 +94,7 @@ export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
export const useModernStrictMode = true;
export const enableUseResourceEffectHook = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
@@ -86,6 +86,7 @@ export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
export const useModernStrictMode = true;
export const enableSiblingPrerendering = false;
export const enableUseResourceEffectHook = false;
// Profiling Only
export const enableProfilerTimer = __PROFILE__;
@@ -84,6 +84,8 @@ export const renameElementSymbol = true;
export const enableShallowPropDiffing = false;
export const enableSiblingPrerendering = false;
export const enableUseResourceEffectHook = false;
// TODO: This must be in sync with the main ReactFeatureFlags file because
// the Test Renderer's value must be the same as the one used by the
// react package.
@@ -81,6 +81,7 @@ export const transitionLaneExpirationMs = 5000;
export const useModernStrictMode = true;
export const enableFabricCompleteRootInCommitPhase = false;
export const enableSiblingPrerendering = false;
export const enableUseResourceEffectHook = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
@@ -97,5 +97,7 @@ export const enableOwnerStacks = false;
export const enableShallowPropDiffing = false;
export const enableSiblingPrerendering = false;
export const enableUseResourceEffectHook = false;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
@@ -125,5 +125,7 @@ export const disableLegacyMode: boolean =
export const enableOwnerStacks = false;
export const enableShallowPropDiffing = false;
export const enableUseResourceEffectHook = true;
// Flow magic to verify the exports of this file match the original version.
((((null: any): ExportsType): FeatureFlagsType): ExportsType);