Search code examples
react-nativeredux-toolkit

Selector memoized returned the root state when called. Redux-ToolKit


I'm using useSelector of reduxtoolkit and everytime I run my app. My app re-renders 5 times and I keep on getting this error. I cant seem to find a way too get rid of this error.

Selector memoized returned the root state when called. This can lead to unnecessary rerenders. Selectors that return the entire state are almost certainly a mistake, as they will cause a rerender whenever anything in state changes.

Image Of Error

AppNavigator.tsx

const AppNavigator: FC<Props> = props => {
  const {loggedIn, busy} = useSelector(getAuthState);
  const dispatch = useDispatch();
  console.log('render');
  useEffect(() => {
    const fetchAuthInfo = async () => {
      try {
        dispatch(updateBusyState(true));
        const token = await getFromAsyncStorage(Keys.AUTH_TOKEN);
        if (!token) {
          return dispatch(updateBusyState(false));
        }

        const {data} = await client.get('/auth/is-auth', {
          headers: {
            Authorization: 'Bearer ' + token,
          },
        });
        dispatch(updateProfile(data.profile));
        dispatch(updateLoggedInState(true));
      } catch (error) {
        console.log('Auth error: ', error);
      }
      dispatch(updateBusyState(false));
    };
    fetchAuthInfo();
  }, []);

  return (
    <NavigationContainer theme={AppTheme}>
      {busy ? (
        <View
          style={{
            ...StyleSheet.absoluteFillObject,
            backgroundColor: colors.OVERLAY,
            zIndex: 1,
            justifyContent: 'center',
            alignItems: 'center',
          }}>
          <Loader />
        </View>
      ) : null}
      {loggedIn ? <TabNavigator /> : <AuthNavigator />}
    </NavigationContainer>
  );
};

Slice.tsx

const slice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    updateProfile(authState, {payload}: PayloadAction<UserProfile | null>) {
      authState.profile = payload;
    },
    updateLoggedInState(authState, {payload}) {
      authState.loggedIn = payload;
    },
    updateBusyState(authState, {payload}: PayloadAction<boolean>) {
      authState.busy = payload;
    },
  },
});

export const {updateLoggedInState, updateProfile, updateBusyState} =
  slice.actions;

export const getAuthState = createSelector(
  (state: RootState) => state,
  ({auth}) => auth,
);

export default slice.reducer;

Solution

  • For something like you are doing here, you really do not need (and should not use) createSelector.

    Instead of

    export const getAuthState = createSelector(
      (state: RootState) => state,
      ({auth}) => auth,
    );
    

    you can just write

    export const getAuthState = (state: RootState) => state.auth;
    

    createSelector is only necessary when your selector is doing heavy computation or creating a new object.

    Also, you should not do something like

    const {loggedIn, busy} = useSelector(getAuthState);
    

    That will also rerender when state.auth.profile.lastName(or, really, anything else in state.auth) changes.

    Instead, do

    const loggedIn = useSelector(rootState => getAuthState(rootState).loggedIn);
    const busy = useSelector(rootState => getAuthState(rootState).busy);