I have a hook useGetFooId
that attempts to get a Foo ID from the Redux store with fallback logic:
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
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 a try/catch
.
See store.subscribe.
Use a useEffect
hook to instantiate a listener that can select the current fooId
value from the store and save it into a local React state. You can surround the store subscription logic in a try/catch
.
import { store } from '../path/to/store';
const useGetFooId = () => {
const [fooIdFromSelector, setFooIdFromSelector] = React.useState(null);
React.useEffect(() => {
try {
const unsubscribe = store.subscribe(() => {
const { fooId } = uiSelector(store.getState());
setFooIdFromSelector(fooId);
});
return unsubscribe;
} catch(error) {
// some error happened during the store subscription
// handle/log/ignore/etc, it's up to you
}
}, []);
const fooIdFromLessReliablePlace = getFooIdFromLessReliablePlace();
const fooIdFromVeryUnreliablePlace = getVeryUnreliableFooId();
return fooIdFromSelector
?? fooIdFromLessReliablePlace
?? fooIdFromVeryUnreliablePlace
?? null;
};