Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useMutableSource hook #18000

Merged
merged 27 commits into from
Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0eb26dd
useMutableSource hook
Feb 19, 2020
8e751b2
Refactored useMutableSource to use update queue
Feb 20, 2020
06a571e
Changed subscribe callback signature to not require the latest snapsh…
Feb 20, 2020
2caa4e9
Handle errors that occur during eager snapshot evaluation
Feb 20, 2020
6263c97
Fixed a typo/bug in setState updater function
Feb 21, 2020
905a9c0
Avoid deopt on changed getSnapshot function unless snapshot also changes
Feb 21, 2020
112ace4
Read mutable source composed hooks from current dispatcher
Feb 21, 2020
6d1dd6a
Split useMutableSource tests into separate suite
Feb 21, 2020
73b2628
Refactor useMutableSource to be more efficient
Feb 23, 2020
5f37cba
Added createMutableSource and useMutableSource exports to new ES entr…
Feb 25, 2020
8f16725
Added React debug tools test for new hook
Feb 25, 2020
8596425
Added a (disabled) test to cover uSM throwing during sync update
Feb 27, 2020
bf9025b
Use a second effect to sync getSnapshot and stateHook values on commit
Mar 2, 2020
38a2044
Always reset state queue when getSnapshot changes
Mar 2, 2020
bf8949e
Always treat reads from source as unsafe when getSnapshot changes
Mar 3, 2020
f231a4b
Pass underlying source to getVersion fn
Mar 5, 2020
0c61fed
Removed unused 'isMount' param from uMS
Mar 5, 2020
160b0a5
Misc. cleanup
Mar 6, 2020
7327787
Whitespace only change
Mar 6, 2020
9c6dd03
Reenabled a pending disabled mutable source test
Mar 6, 2020
cd2ef30
Removed createMutableSource and useMutableSource exports from React s…
Mar 6, 2020
153a8f1
Defer getWorkInProgressRoot() call unless root is needed
Mar 6, 2020
293aa74
Misc. cleanup
Mar 6, 2020
aa46144
Replaced .warn() with .error() for mutable source unsubscribe check
Mar 6, 2020
0ef4bdb
Added an additional muti renderer test
Mar 10, 2020
45ed506
Added new failing tests (pending a separate fix to React)
Mar 10, 2020
dee1164
Merged master and resolved useEvent conflicts
Mar 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
*/

import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
ReactProviderType,
ReactEventResponder,
Expand Down Expand Up @@ -72,6 +75,16 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
Dispatcher.useDebugValue(null);
Dispatcher.useCallback(() => {});
Dispatcher.useMemo(() => null);
Dispatcher.useMutableSource(
{
_source: {},
_getVersion: () => 1,
_workInProgressVersionPrimary: null,
_workInProgressVersionSecondary: null,
},
() => null,
() => () => {},
);
} finally {
readHookLog = hookLog;
hookLog = [];
Expand Down Expand Up @@ -229,6 +242,23 @@ function useMemo<T>(
return value;
}

function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
// useMutableSource() composes multiple hooks internally.
// Advance the current hook index the same number of times
// so that subsequent hooks have the right memoized state.
nextHook(); // MutableSource
nextHook(); // State
nextHook(); // Effect
nextHook(); // Effect
const value = getSnapshot(source._source);
Copy link
Contributor Author

@bvaughn bvaughn Feb 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: This isn't quite right, since it might return a copy of the data that's too new.

This probably doesn't matter though. The debug hooks aren't really accurate anyway at the moment (e.g. useCallback always returns a new function too). Since rendering is shallow it shouldn't really make a difference.

I'm going to just assumet his doesn't matter.

hookLog.push({primitive: 'MutableSource', stackError: new Error(), value});
return value;
}

function useResponder(
responder: ReactEventResponder<any, any>,
listenerProps: Object,
Expand Down Expand Up @@ -299,6 +329,7 @@ const Dispatcher: DispatcherType = {
useState,
useResponder,
useTransition,
useMutableSource,
useDeferredValue,
useEvent,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,4 +785,38 @@ describe('ReactHooksInspectionIntegration', () => {
},
]);
});

if (__EXPERIMENTAL__) {
it('should support composite useMutableSource hook', () => {
const mutableSource = React.createMutableSource({}, () => 1);
function Foo(props) {
React.useMutableSource(
mutableSource,
() => 'snapshot',
() => {},
);
React.useMemo(() => 'memo', []);
return <div />;
}
let renderer = ReactTestRenderer.create(<Foo />);
let childFiber = renderer.root.findByType(Foo)._currentFiber();
let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
expect(tree).toEqual([
{
id: 0,
isStateEditable: false,
name: 'MutableSource',
value: 'snapshot',
subHooks: [],
},
{
id: 1,
isStateEditable: false,
name: 'Memo',
value: 'memo',
subHooks: [],
},
]);
});
}
});
17 changes: 17 additions & 0 deletions packages/react-dom/src/server/ReactPartialRendererHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import type {
} from 'react-reconciler/src/ReactFiberHooks';
import type {ThreadID} from './ReactThreadIDAllocator';
import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
ReactEventResponderListener,
} from 'shared/ReactTypes';
Expand Down Expand Up @@ -461,6 +464,18 @@ function useResponder(responder, props): ReactEventResponderListener<any, any> {
};
}

// TODO Decide on how to implement this hook for server rendering.
// If a mutation occurs during render, consider triggering a Suspense boundary
// and falling back to client rendering.
function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
resolveCurrentlyRenderingComponent();
return getSnapshot(source._source);
bvaughn marked this conversation as resolved.
Show resolved Hide resolved
}

function useDeferredValue<T>(value: T, config: TimeoutConfig | null | void): T {
resolveCurrentlyRenderingComponent();
return value;
Expand Down Expand Up @@ -510,4 +525,6 @@ export const Dispatcher: DispatcherType = {
useDeferredValue,
useTransition,
useEvent,
// Subscriptions are not setup in a server environment.
useMutableSource,
};
4 changes: 2 additions & 2 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ import {
markSpawnedWork,
requestCurrentTimeForUpdate,
retryDehydratedSuspenseBoundary,
scheduleWork,
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
markUnprocessedUpdateTime,
} from './ReactFiberWorkLoop';
Expand Down Expand Up @@ -2121,7 +2121,7 @@ function updateDehydratedSuspenseComponent(
// at even higher pri.
let attemptHydrationAtExpirationTime = renderExpirationTime + 1;
suspenseState.retryTime = attemptHydrationAtExpirationTime;
scheduleWork(current, attemptHydrationAtExpirationTime);
scheduleUpdateOnFiber(current, attemptHydrationAtExpirationTime);
// TODO: Early abort this render.
} else {
// We have already tried to ping at a higher priority than we're rendering with
Expand Down
8 changes: 4 additions & 4 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import {readContext} from './ReactFiberNewContext';
import {
requestCurrentTimeForUpdate,
computeExpirationForFiber,
scheduleWork,
scheduleUpdateOnFiber,
} from './ReactFiberWorkLoop';
import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';

Expand Down Expand Up @@ -200,7 +200,7 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
scheduleUpdateOnFiber(fiber, expirationTime);
},
enqueueReplaceState(inst, payload, callback) {
const fiber = getInstance(inst);
Expand All @@ -224,7 +224,7 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
scheduleUpdateOnFiber(fiber, expirationTime);
},
enqueueForceUpdate(inst, callback) {
const fiber = getInstance(inst);
Expand All @@ -247,7 +247,7 @@ const classComponentUpdater = {
}

enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
scheduleUpdateOnFiber(fiber, expirationTime);
},
};

Expand Down
2 changes: 2 additions & 0 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
SuspenseListRenderState,
} from './ReactFiberSuspenseComponent';
import type {SuspenseContext} from './ReactFiberSuspenseContext';
import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource';

import {now} from './SchedulerWithReactIntegration';

Expand Down Expand Up @@ -662,6 +663,7 @@ function completeWork(
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
resetMutableSourceWorkInProgressVersions();
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
Expand Down
Loading