Visitar URL original
Checked for missed updates in effect create phase · meta/relay@6165081 · GitHub
Skip to content

Commit 6165081

Browse files
Jack Popefacebook-github-bot
authored andcommitted
Checked for missed updates in effect create phase
Summary: There are regressions in the useResourceEffect experiment. We believe this may be due to a behavior difference rather than a problem with useResourceEffect itself. The two branches are meant to have identical behavior but it looks like there is a regression when a component mounts and the store updates before effects run. In the case of missed updates on mount, the original behavior is: - update `useEffect` runs - check for missed updates - with missed updates, setState and return - create `useEffect` runs - `storeSubscriptionRef` was not assigned in update, due to early return for missed updates - `subscribeToSnapshot` called - *(note: comments in the update effect claim this should not be done until second render when missed updates are synced)* - `storeSubscriptionRef.current` is set - new render due to missed updates setState - update `useEffect` runs - `storeSubscriptionRef` is set but environment/selector have not changed, so return early - create `useEffect` runs - `storeSubscriptionRef` is set, so return early The useResourceEffect behavior (before this diff) is: - useResourceEffect create phase runs - `subscribeToSnapshot` is called and resource is created - **missed updates are not checked** - *¿maybe some other update causes a rerender?* - `useResourceEffect` update phase runs - if environment/selector have changed, return early and **still don't check for missed updates** - else, check for missed updates... The useResourceEffect behavior (with this diff) is: - `useResourceEffect` create phase runs - check for missed updates - with missed updates, setState and setup resource object with `subscribed:false` - new render due to missed updates setState - `useResourceEffect` update phase runs - check for missed updates - updates should have been synced by now from previous render - `subscribeToSnapshot` called and subscription object is updated Iterating on useResourceEffect experiments. We may be dropping missed update syncs here due to not checking for missed updates in the create phase. Moving the check to happen in both create/update. Reviewed By: poteto, tyao1 Differential Revision: D68562780 fbshipit-source-id: 63e5165cff4b4e931779e2a1b2da7fbebbbdf888
1 parent 61c7de9 commit 6165081

1 file changed

Lines changed: 60 additions & 30 deletions

File tree

‎packages/react-relay/relay-hooks/useFragmentInternal_EXPERIMENTAL.js‎

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const invariant = require('invariant');
3030
const {
3131
// $FlowFixMe[prop-missing] this is only available in custom builds
3232
experimental_useResourceEffect: useResourceEffect,
33+
useCallback,
3334
useDebugValue,
3435
useEffect,
3536
useMemo,
@@ -582,28 +583,74 @@ hook useFragmentInternal_EXPERIMENTAL(
582583
selector: ?ReaderSelector,
583584
environment: IEnvironment,
584585
} | null>(null);
585-
586586
if (
587587
RelayFeatureFlags.ENABLE_RESOURCE_EFFECTS &&
588588
typeof useResourceEffect === 'function'
589589
) {
590590
// $FlowExpectedError[react-rule-hook] - the condition is static
591-
useResourceEffect(
591+
const getStateForSubscription = useCallback(() => {
592+
// The FragmentState that we'll actually subscribe to. Note that it's possible that
593+
// a concurrent modification to the store didn't affect the snapshot _data_ (so we don't
594+
// need to re-render), but did affect the seen records. So if there were missed updates
595+
// we use that state to subscribe.
596+
let stateForSubscription: FragmentState = state;
597+
// No subscription yet or the selector has changed, so we need to subscribe
598+
// first check for updates since the state was rendered
599+
const updates = handleMissedUpdates(state.environment, state);
600+
if (updates !== null) {
601+
const [didMissUpdates, updatedState] = updates;
602+
// TODO: didMissUpdates only checks for changes to snapshot data, but it's possible
603+
// that other snapshot properties may have changed that should also trigger a re-render,
604+
// such as changed missing resolver fields, missing client edges, etc.
605+
// A potential alternative is for handleMissedUpdates() to recycle the entire state
606+
// value, and return the new (recycled) state only if there was some change. In that
607+
// case the code would always setState if something in the snapshot changed, in addition
608+
// to using the latest snapshot to subscribe.
609+
if (didMissUpdates) {
610+
setState(updatedState);
611+
// We missed updates, we're going to render again anyway so wait until then to subscribe
612+
return null;
613+
}
614+
stateForSubscription = updatedState;
615+
}
616+
617+
return stateForSubscription;
618+
}, [state]);
619+
// $FlowExpectedError[react-rule-hook] - the condition is static
620+
useResourceEffect<{
621+
subscribed: boolean,
622+
dispose: () => void,
623+
selector: ?ReaderSelector,
624+
environment: IEnvironment,
625+
}>(
592626
() => {
627+
const stateForSubscription = getStateForSubscription();
628+
if (stateForSubscription == null) {
629+
return {
630+
subscribed: false,
631+
dispose: () => {},
632+
selector: state.selector,
633+
environment: state.environment,
634+
};
635+
}
636+
637+
const dispose = subscribeToSnapshot(
638+
state.environment,
639+
stateForSubscription,
640+
setState,
641+
);
593642
return {
594-
dispose:
595-
state.kind !== 'bailout'
596-
? subscribeToSnapshot(state.environment, state, setState)
597-
: () => {},
643+
subscribed: true,
644+
dispose,
598645
selector: state.selector,
599646
environment: state.environment,
600647
};
601648
},
602-
// NOTE: this intentionally has no dependencies
603649
[],
604650
// $FlowFixMe[missing-local-annot] - Untyped in OSS
605651
storeSubscription => {
606652
if (
653+
storeSubscription.subscribed === true &&
607654
state.environment === storeSubscription.environment &&
608655
state.selector === storeSubscription.selector
609656
) {
@@ -616,35 +663,17 @@ hook useFragmentInternal_EXPERIMENTAL(
616663
if (state.kind === 'bailout') {
617664
return;
618665
}
619-
// The FragmentState that we'll actually subscribe to. Note that it's possible that
620-
// a concurrent modification to the store didn't affect the snapshot _data_ (so we don't
621-
// need to re-render), but did affect the seen records. So if there were missed updates
622-
// we use that state to subscribe.
623-
let stateForSubscription: FragmentState = state;
624-
// No subscription yet or the selector has changed, so we need to subscribe
625-
// first check for updates since the state was rendered
626-
const updates = handleMissedUpdates(state.environment, state);
627-
if (updates !== null) {
628-
const [didMissUpdates, updatedState] = updates;
629-
// TODO: didMissUpdates only checks for changes to snapshot data, but it's possible
630-
// that other snapshot properties may have changed that should also trigger a re-render,
631-
// such as changed missing resolver fields, missing client edges, etc.
632-
// A potential alternative is for handleMissedUpdates() to recycle the entire state
633-
// value, and return the new (recycled) state only if there was some change. In that
634-
// case the code would always setState if something in the snapshot changed, in addition
635-
// to using the latest snapshot to subscribe.
636-
if (didMissUpdates) {
637-
setState(updatedState);
638-
// We missed updates, we're going to render again anyway so wait until then to subscribe
639-
return;
640-
}
641-
stateForSubscription = updatedState;
666+
667+
const stateForSubscription = getStateForSubscription();
668+
if (stateForSubscription == null) {
669+
return;
642670
}
643671
const dispose = subscribeToSnapshot(
644672
state.environment,
645673
stateForSubscription,
646674
setState,
647675
);
676+
storeSubscription.subscribed = true;
648677
storeSubscription.dispose = dispose;
649678
storeSubscription.selector = state.selector;
650679
storeSubscription.environment = state.environment;
@@ -653,6 +682,7 @@ hook useFragmentInternal_EXPERIMENTAL(
653682
// $FlowFixMe[missing-local-annot] - Untyped in OSS
654683
storeSubscription => {
655684
storeSubscription.dispose();
685+
storeSubscription.subscribed = false;
656686
},
657687
);
658688
} else {

0 commit comments

Comments
 (0)