in packages/recoil/hooks/Recoil_Hooks.js [101:294]
function useRecoilInterface_DEPRECATED(): RecoilInterface {
const componentName = useComponentName();
const storeRef = useStoreRef();
const [, forceUpdate] = useState([]);
const recoilValuesUsed = useRef<$ReadOnlySet<NodeKey>>(new Set());
recoilValuesUsed.current = new Set(); // Track the RecoilValues used just during this render
const previousSubscriptions = useRef<$ReadOnlySet<NodeKey>>(new Set());
const subscriptions = useRef<Map<NodeKey, ComponentSubscription>>(new Map());
const unsubscribeFrom = useCallback(
key => {
const sub = subscriptions.current.get(key);
if (sub) {
sub.release();
subscriptions.current.delete(key);
}
},
[subscriptions],
);
const updateState = useCallback((_state, key) => {
if (subscriptions.current.has(key)) {
forceUpdate([]);
}
}, []);
// Effect to add/remove subscriptions as nodes are used
useEffect(() => {
const store = storeRef.current;
differenceSets(
recoilValuesUsed.current,
previousSubscriptions.current,
).forEach(key => {
if (subscriptions.current.has(key)) {
expectationViolation(`Double subscription to RecoilValue "${key}"`);
return;
}
const sub = subscribeToRecoilValue(
store,
new AbstractRecoilValue(key),
state => updateState(state, key),
componentName,
);
subscriptions.current.set(key, sub);
/**
* Since we're subscribing in an effect we need to update to the latest
* value of the atom since it may have changed since we rendered. We can
* go ahead and do that now, unless we're in the middle of a batch --
* in which case we should do it at the end of the batch, due to the
* following edge case: Suppose an atom is updated in another useEffect
* of this same component. Then the following sequence of events occur:
* 1. Atom is updated and subs fired (but we may not be subscribed
* yet depending on order of effects, so we miss this) Updated value
* is now in nextTree, but not currentTree.
* 2. This effect happens. We subscribe and update.
* 3. From the update we re-render and read currentTree, with old value.
* 4. Batcher's effect sets currentTree to nextTree.
* In this sequence we miss the update. To avoid that, add the update
* to queuedComponentCallback if a batch is in progress.
*/
// FIXME delete queuedComponentCallbacks_DEPRECATED when deleting useInterface.
const state = store.getState();
if (state.nextTree) {
store.getState().queuedComponentCallbacks_DEPRECATED.push(() => {
updateState(store.getState(), key);
});
} else {
updateState(store.getState(), key);
}
});
differenceSets(
previousSubscriptions.current,
recoilValuesUsed.current,
).forEach(key => {
unsubscribeFrom(key);
});
previousSubscriptions.current = recoilValuesUsed.current;
});
// Effect to unsubscribe from all when unmounting
useEffect(() => {
const currentSubscriptions = subscriptions.current;
// Restore subscriptions that were cleared due to StrictMode running this effect twice
differenceSets(
recoilValuesUsed.current,
new Set(currentSubscriptions.keys()),
).forEach(key => {
const sub = subscribeToRecoilValue(
storeRef.current,
new AbstractRecoilValue(key),
state => updateState(state, key),
componentName,
);
currentSubscriptions.set(key, sub);
});
return () => currentSubscriptions.forEach((_, key) => unsubscribeFrom(key));
}, [componentName, storeRef, unsubscribeFrom, updateState]);
return useMemo(() => {
// eslint-disable-next-line no-shadow
function useSetRecoilState<T>(
recoilState: RecoilState<T>,
): SetterOrUpdater<T> {
if (__DEV__) {
validateRecoilValue(recoilState, 'useSetRecoilState');
}
return (
newValueOrUpdater: (T => T | DefaultValue) | T | DefaultValue,
) => {
setRecoilValue(storeRef.current, recoilState, newValueOrUpdater);
};
}
// eslint-disable-next-line no-shadow
function useResetRecoilState<T>(recoilState: RecoilState<T>): Resetter {
if (__DEV__) {
validateRecoilValue(recoilState, 'useResetRecoilState');
}
return () => setRecoilValue(storeRef.current, recoilState, DEFAULT_VALUE);
}
// eslint-disable-next-line no-shadow
function useRecoilValueLoadable<T>(
recoilValue: RecoilValue<T>,
): Loadable<T> {
if (__DEV__) {
validateRecoilValue(recoilValue, 'useRecoilValueLoadable');
}
if (!recoilValuesUsed.current.has(recoilValue.key)) {
recoilValuesUsed.current = setByAddingToSet(
recoilValuesUsed.current,
recoilValue.key,
);
}
// TODO Restore optimization to memoize lookup
const storeState = storeRef.current.getState();
return getRecoilValueAsLoadable(
storeRef.current,
recoilValue,
reactMode().early
? storeState.nextTree ?? storeState.currentTree
: storeState.currentTree,
);
}
// eslint-disable-next-line no-shadow
function useRecoilValue<T>(recoilValue: RecoilValue<T>): T {
if (__DEV__) {
validateRecoilValue(recoilValue, 'useRecoilValue');
}
const loadable = useRecoilValueLoadable(recoilValue);
return handleLoadable(loadable, recoilValue, storeRef);
}
// eslint-disable-next-line no-shadow
function useRecoilState<T>(
recoilState: RecoilState<T>,
): [T, SetterOrUpdater<T>] {
if (__DEV__) {
validateRecoilValue(recoilState, 'useRecoilState');
}
return [useRecoilValue(recoilState), useSetRecoilState(recoilState)];
}
// eslint-disable-next-line no-shadow
function useRecoilStateLoadable<T>(
recoilState: RecoilState<T>,
): [Loadable<T>, SetterOrUpdater<T>] {
if (__DEV__) {
validateRecoilValue(recoilState, 'useRecoilStateLoadable');
}
return [
useRecoilValueLoadable(recoilState),
useSetRecoilState(recoilState),
];
}
return {
getRecoilValue: useRecoilValue,
getRecoilValueLoadable: useRecoilValueLoadable,
getRecoilState: useRecoilState,
getRecoilStateLoadable: useRecoilStateLoadable,
getSetRecoilState: useSetRecoilState,
getResetRecoilState: useResetRecoilState,
};
}, [recoilValuesUsed, storeRef]);
}