开发者问题收集

未捕获的类型错误:无法读取 Navbar.jsx 中未定义的属性(读取“isAuthenticated”)

2023-05-31
199

实际上,我希望在导航栏中同时显示登录和注销。当它显示注销时,如果用户单击它,它应该可以正常工作。但它不能正常工作。相反,它显示错误。 isAuthenticated 在 Reducer.js 中可用 Error

Navbar.jsx:

import React, { useState } from "react";
import { NavLink, useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { logout } from "../../redux/actions";
import { links } from "../../data";
import "./Navbar.css";

const Navbar = () => {
  const [showMenu, setShowMenu] = useState(false);
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const toggleMenu = () => {
    setShowMenu(!showMenu);
  };

  const handleLogout = () => {
    dispatch(logout());
    navigate("/");
  };

  let filteredLinks = links;

  if (isAuthenticated) {
    filteredLinks = links.filter((link) => link.name !== "Login");
  } else {
    filteredLinks = links.filter((link) => link.name !== "Logout");
  }

  return (
    <nav className="nav">
      <div className={`${showMenu ? "nav_menu show-menu" : "nav_menu"}`}>
        <ul className="nav_list">
          {filteredLinks.map(({ id, name, icon, path }) => (
            <li className="nav_item" key={id}>
              {name === "Logout" ? (
                <a className="nav_link" onClick={handleLogout}>
                  {icon}
                  <h3 className="nav_name">{name}</h3>
                </a>
              ) : (
                <NavLink
                  to={path}
                  className={({ isActive }) =>
                    isActive ? "nav_link active-nav" : "nav_link"
                  }
                  onClick={toggleMenu}
                >
                  {icon}
                  <h3 className="nav_name">{name}</h3>
                </NavLink>
              )}
            </li>
          ))}
        </ul>
      </div>

      <div
        className={`${showMenu ? "nav_toggle animate-toggle" : "nav_toggle"}`}
        onClick={toggleMenu}
      >
        <span></span>
        <span></span>
        <span></span>
      </div>
    </nav>
  );
};

export default Navbar;

data.jsx:

import {
  FaHome,
  FaUser,
  FaFolderOpen,
  FaEnvelopeOpen,
  FaBriefcase,
  FaGraduationCap,
  FaCode,
  FaLock,
  FaGithub,
} from "react-icons/fa";
import { FiFileText, FiUser, FiLogIn, FiLogOut, FiExternalLink } from "react-icons/fi";

export const links = [
  {
    id: 1,
    name: "Home",
    icon: <FaHome className="nav_icon" />,
    path: "/",
  },

  {
    id: 2,
    name: "About",
    icon: <FaUser className="nav_icon" />,
    path: "/about",
  },

  {
    id: 3,
    name: "Projects",
    icon: <FaFolderOpen className="nav_icon" />,
    path: "/portfolio",
  },

  {
    id: 4,
    name: "Contact",
    icon: <FaEnvelopeOpen className="nav_icon" />,
    path: "/contact",
  },
  // {
  //   id: 5,
  //   name: "SignUp",
  //   icon: <FiLogIn className="nav_icon" />,
  //   path: "/signup",
  // },
  {
    id: 6,
    name: "Login",
    icon: <FaLock className="nav_icon" />,
    path: "/login",
  },
  {
    id: 7,
    name: "logout",
    icon: <FiLogOut className="nav_icon" />,
    path: "/",
  },
];

Login.jsx:

import React, { useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { toast, ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import "./Login.css";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  const handleEmailChange = (e) => {
    setEmail(e.target.value);
  };

  const handlePasswordChange = (e) => {
    setPassword(e.target.value);
  };

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      const response = await axios.post("http://localhost:8080/login", {
        email,
        password,
      });

      // Assuming the response contains a token
      const token = response.data.token;

      // Save the token to localStorage
      localStorage.setItem("token", token);

      // Handle successful login here (e.g., redirect to admin dashboard)
      toast.success("Login successful", {
        position: "top-right",
        autoClose: 1000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: "light",
        onClose: () => {
          navigate("/admindashboard"); // Navigate after the toast message is closed
          setTimeout(() => {
            window.location.reload();
          }, 1000); // Delay the reload by 1000 milliseconds (1 second)
        },
      });
      // Set isLoggedIn state to true
    } catch (error) {
      // Handle login error here (e.g., display error message)
      toast.error("Login failed");
      console.log("Error:", error.response.data.message);
    }
  };

  return (
    <section className="login section">
      <h2 className="section_title">
        Login For <span>Admin</span>
      </h2>
      <form className="login_form" onSubmit={handleLogin}>
        <div className="login_form_input-group">
          <div className="form_input-div">
            <label className="login_form-title">Email:</label>
            <input
              type="email"
              placeholder="Your Email"
              className="form_control"
              value={email}
              onChange={handleEmailChange}
            />
          </div>
          <div className="form_input-div">
            <label className="login_form-title">Password:</label>
            <input
              type="password"
              placeholder="Your Password"
              className="form_control"
              value={password}
              onChange={handlePasswordChange}
            />
          </div>
        </div>
        <button className="button login_button" type="submit">
          <span className="button_name">Login</span>
        </button>
      </form>
      <ToastContainer />
    </section>
  );
};

export default Login;

actions.js:

// authActions.js

import axios from "axios";
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";

export const register = (name, email, password) => async (dispatch) => {
  try {
    const response = await axios.post("/register", { name, email, password });
    dispatch({ type: REGISTER_SUCCESS, payload: response.data.token });
  } catch (error) {
    dispatch({ type: REGISTER_FAIL, payload: error.response.data.message });
  }
};

// Login action creator
export const login = (email, password) => async (dispatch) => {
  try {
    const response = await axios.post("/login", { email, password });
    dispatch({ type: LOGIN_SUCCESS, payload: response.data.token });
  } catch (error) {
    dispatch({ type: LOGIN_FAIL, payload: error.response.data.message });
  }
};

// Logout action creator
export const logout = () => ({ type: LOGOUT });

reducers.js:

import {
  REGISTER_SUCCESS,
  REGISTER_FAIL,
  LOGIN_SUCCESS,
  LOGIN_FAIL,
  LOGOUT,
} from "./actions";

const initialState = {
  token: null,
  isAuthenticated: false,
  error: null,
};

const authReducer = (state = initialState, action) => {
  switch (action.type) {
    case REGISTER_SUCCESS:
    case LOGIN_SUCCESS:
      return {
        ...state,
        token: action.payload,
        isAuthenticated: true,
        error: null,
      };
    case REGISTER_FAIL:
    case LOGIN_FAIL:
      return {
        ...state,
        token: null,
        isAuthenticated: false,
        error: action.payload,
      };
    case LOGOUT:
      return {
        ...state,
        token: null,
        isAuthenticated: false,
        error: null,
      };
    default:
      return state;
  }
};

export default authReducer;

rootreducer.js:

import { combineReducers } from "redux";
import authReducer from "./authReducer";

const rootReducer = combineReducers({
  auth: authReducer,
});

export default rootReducer;

store.js:

import { configureStore } from "@reduxjs/toolkit";
import thunk from "redux-thunk";
import rootReducer from "./reducers";

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk],
});

export default store;

main.jsx:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { Provider } from "react-redux";
import store from "./redux/store.js";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);

我想要一个可以帮助我克服这个问题的解决方案。如果有人能帮助我摆脱这个问题,那将非常有帮助。提前致谢

2个回答

您的 rootReducer 实际上是您的 authReducer ,因此 state.auth 未定义。

您需要从状态中获取 isAuthenticated

它应该是您的 NavBar.jsx 中的类似内容

state.auth.isAuthenticated ==> state.isAuthenticated

Amir Sorayaei
2023-05-31

问题

rootReducer authReducer ,例如 import rootReducer from './reducers'; ,因此没有 state.auth 可供选择。

reducers.js:

const initialState = {
  token: null,
  isAuthenticated: false,
  error: null,
};

const authReducer = (state = initialState, action) => {
  ...
};

export default authReducer; // <-- default export

store.js

import rootReducer from "./reducers"; // <-- default import

const store = configureStore({
  reducer: rootReducer, // <-- authReducer => state
  middleware: [thunk],
});

解决方案

store.js 应从 ./rootreducer" 导入默认导出的 Reducer 函数。

import { configureStore } from "@reduxjs/toolkit";
import thunk from "redux-thunk";
import rootReducer from "./rootreducer";

const store = configureStore({
  reducer: rootReducer, // <-- { auth: authReducer }
  middleware: [thunk],
});

export default store;

Navbar 组件应能够直接从 state.auth 正确选择 isAuthenticated

const { isAuthenticated } = useSelector((state) => state.auth);
Drew Reese
2023-05-31