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

4

8

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;
Burnet answered 16/6, 2023 at 18:14 Comment(0)
C
17

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);
Camarata answered 16/6, 2023 at 19:3 Comment(4)
PS: that said, you should only ever get this error when you call useSelector(state => state) somewhere. Are you sure you are not doing that anywhere too?Camarata
How exactly does redux compare previous state with the current one in order to rerender? I have read from doc that its done via === operator, but what are its operands? previous state (how exactly is stored) and current one?Hawes
@Hawes The moment it gets a new selector result it still knows the old one, so it literally does newResult === result to compare them.Camarata
sounds about rightHawes
H
2

Because you didn't give a name, back to your store index file add auth as a name:

store/index.ts


const store = configureStore({
  reducer: {
  auth: authReducer
  },
});

store/auth.ts

 export const getAuthState = createSelector(
  (state: RootState) => state.auth,
  authState => authState,
);
Householder answered 16/8, 2023 at 9:21 Comment(0)
S
0

create-react-app with react-typescript-router template had the following in generated supports/Persistence.tsx file

const state = useAppSelector((state: RootState) => state)
const count = String(state.counter.value)

Which caused the following error to popup in the browser console

Selector unknown 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.

Don't deal with the whole root state in Persistence.tsx, changing it to the following should make the error go away

const count = useAppSelector((state: RootState) => state.counter.value)
Sirmons answered 26/2, 2024 at 6:2 Comment(0)
R
0

I was facing the same issue

Selector memoized returned the root state when called. This can lead to unnecessary rerenders.

The accepted answer wasn't working me. What worked for is a combination of two answers.

firt if your store.ts is like this

const store = configureStore({ reducer: authReducer, });

then you have to change it to look like

const store = configureStore({ reducer: { auth: authReducer, }, });

Then in your auth.ts change the following

`export const getAuthState = createSelector(

(state: RootState) => state.auth, authState => authState,

);` to look like below

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

Round answered 1/4, 2024 at 10:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.