I have a hook useGetFooId
that attempts to get a Foo ID from the Redux store with fallback logic:
- If the Foo ID is set in the Redux store, it returns the ID from there (but it’s expected that in some cases, it’s not set in the Redux store).
- Otherwise, if the Foo ID is not available from the Redux store, it falls back to trying to get the ID from a few other, less reliable places.
It looks something like:
const useGetFooId = () => {
const { fooId: fooIdFromSelector } = useSelector(uiSelector);
const fooIdFromLessReliablePlace = getFooIdFromLessReliablePlace();
const fooIdFromVeryUnreliablePlace = getVeryUnreliableFooId();
return fooIdFromSelector
?? fooIdFromLessReliablePlace
?? fooIdFromVeryUnreliablePlace
?? null;
}
Now, I want to make useGetFooId
safe to call from outside of the Redux Provider. (The Foo ID is relevant to some display logic in an error boundary component at the root of the application). Right now, if it’s called from outside the Provider, it gets this error: Cannot read properties of null (reading 'store')
.
The desired behavior is basically: If useGetFooId
is called from outside of the selector, it uses the same fallback logic as if fooId
was undefined in the selector (so, it should return fooIdFromLessReliablePlace ?? fooIdFromVeryUnreliablePlace ?? null
).
Is there a supported way to conditionally access the Redux store only if the Provider is available, or something? I originally thought to wrap it in try/catch, but it seems as though this violates the rules of hooks: https://github.com/facebook/react/issues/16026
2
Answers
Rewrite your custom to subscribe to changes from the store using the store object directly instead of the
useSelector
hook which can’t be called conditionally, e.g. can’t be called conditionally in atry/catch
.See store.subscribe.
Use a
useEffect
hook to instantiate a listener that can select the currentfooId
value from the store and save it into a local React state. You can surround the store subscription logic in atry/catch
.You only need the logic outside of the provider, and not the selector, because:
Create a function called
getFooId
that optionally receives anid
(id
can also beundefined
, and use it in the custom hook, and directly in the error boundary (id
would beundefined
):