javascriptreduxreact-reduxredux-toolkit

@reduxjs/toolkit - UseSelector returning Undefined instead of Google Username


store/index.js

import { configureStore } from "@reduxjs/toolkit";
import rootReducer from "../reducers/index";

const store = configureStore({
  reducer: {
    user: rootReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: false,
    }),
});

export default store;

userSlice.js

import { createSlice } from "@reduxjs/toolkit";

export const INITIAL_STATE = {
  name: "Hello World",
  email: "",
  photo: "",
};

export const userSlice = createSlice({
  name: "user",
  initialState: INITIAL_STATE,
  reducers: {
    setUserLoginDetails: (state, action) => {
      console.log("Initial", state.name);
      state.name = action.payload.name;
      state.email = action.payload.email;
      state.photo = action.payload.photo;
      console.log("Updated", state.name);
    },

    setSignOutState: (state) => {
      state.name = null;
      state.email = null;
      state.photo = null;
    },
  },
});

export const { setUserLoginDetails, setSignOutState } = userSlice.actions;

export const selectUserName = (state) => state.user.name;
export const selectUserEmail = (state) => state.user.email;
export const selectUserPhoto = (state) => state.user.photo;

export default userSlice.reducer;

AuthContext.js

import React, { useContext, createContext, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
  GoogleAuthProvider,
  signInWithPopup,
  signInWithRedirect,
  signOut,
  onAuthStateChanged,
} from "firebase/auth";
import { auth } from "../firebase";
import { setUserLoginDetails, selectUserName } from "../reducers/userSlice";
import store from "../store/index";

const AuthContext = createContext();

export const AuthContextProvider = ({ children }) => {
  const dispatch = useDispatch();
  const username = useSelector(selectUserName);

  const googleSignIn = () => {
    const provider = new GoogleAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        console.log("Check Initial Store", store.getState());
        console.log("Check Initial Name", username);
        setUser(result.user);
        console.log("Check Updated Store", store.getState());
        console.log("Check Updated Name", username);
      })
      .catch((e) => {
        const eCode = e.code;
        const eMessage = e.message;
        const eEmail = e.email;
        const eCredential = e.credential;
      });
  };

  const setUser = (user) => {
    dispatch(
      setUserLoginDetails({
        name: user.displayName,
        email: user.email,
        photo: user.photoURL,
      })
    );
  };

  return (
    <AuthContext.Provider value={{ googleSignIn, username }}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthContext;

export const UserAuth = () => {
  return useContext(AuthContext);
};

App.js

import {
  BrowserRouter as Router,
  Route,
  Routes,
  Navigate,
} from "react-router-dom";
import "./App.css";
import Header from "./components/Header";
import Home from "./components/Home";
import Login from "./components/Login";
import { AuthContextProvider, UserAuth } from "./context/AuthContext";
import { useEffect } from "react";
//import { useSelector } from "react-redux";
//import { selectUserName } from "./reducers/userSlice";
import { onAuthStateChanged } from "firebase/auth";

function App(props) {

  const { username } = UserAuth();

  useEffect(() => {
    //if (username != null) {
      console.log("User name is", username);
    //}
  }, [username]);

  return (
    <div className="App">
      <AuthContextProvider>
        <Router>
          <Routes>
            <Route
              exact
              path="/"
              element={/*username ? <Navigate to="/home" /> : */<Login />}
            />
            <Route
              path="/home"
              element={
                /*!username ? (
                  <Navigate to="/" />
                ) :*/ (
                  <>
                    <Header />
                    <Home />
                  </>
                )
              }
            />
            {/*<Route path="/redirect" element={<Navigate to="/home" />} />*/}
          </Routes>
        </Router>
      </AuthContextProvider>
    </div>
  );
}

export default App;

The problem lies in AuthContext.js, where I am attempting to select the current user name via useSelector(SelectUserName) from userSlice.js, which is (state) => state.user.name. The console.log outputted the log in the attached image:

console.log

Edit: I forgot to add my reducers/index.js so here you go.

reducers/index.js

import { combineReducers } from "redux";
import userReducer from "./userSlice";

const rootReducer = combineReducers({
  userState: userReducer,
});

export default rootReducer;

Thanks a lot everyone for the response. I've tried converting this under userSlice.js:

from selectUserName = (state) => state.user.name; to selectUserName = (state) => state.user.userState.name;

And I was able to get the username proper, rather than unknown. Any further suggestion will be greatly appreciated.


Solution

  • Based on the screenshot you've included in your post it is clear that the Redux store has the following structure:

    {
      user: {
        userState: {
          email: "....",
          name: "....",
          photo: "....",
        },
      },
    }
    

    In other words, to select the name state it is not state.user.name, but rather state.user.userState.name.

    You can fix the selector functions to select the appropriate state using the correct path.

    Example:

    export const selectUserName = (state) => state.user.userState.name;
    export const selectUserEmail = (state) => state.user.userState.email;
    export const selectUserPhoto = (state) => state.user.userState.photo;
    

    It might just be more likely the case that you inadvertently "nested" this userSlice.reducer when exporting/combining into the rootReducer. It looks like you merged/combined a userState reducer when creating the rootReducer and then merged the root reducer as user when configuring the store.

    The root reducer and store configuration should look similar to the following based on your current selector functions:

    ../userSlice.js selectors

    export const selectUserName = (state) => state.user.name;
    export const selectUserEmail = (state) => state.user.email;
    export const selectUserPhoto = (state) => state.user.photo;
    
    export default userSlice.reducer;
    

    ../reducers/index.js

    import { combineReducers } from 'redux';
    import userReducer from '../userSlice';
    
    const rootReducer = combineReducers({
      user: userReducer,
    });
    
    export default rootReducer;
    

    store/index.js

    import { configureStore } from "@reduxjs/toolkit";
    import rootReducer from "../reducers";
    
    const store = configureStore({
      reducer: rootReducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          serializableCheck: false,
        }),
    });
    
    export default store;