找不到 redux 存储并且无法在 React 中呈现特定路径的组件

redux store not found and not able to render a component for a specific path in React

我正在 MERN 应用程序中实现密码重置功能。当用户输入他们想要重置密码的电子邮件地址时,他们会在邮件中收到一个休息密码 link。现在,当他们访问 link 时,他们应该会看到呈现在屏幕上的 PasswordResetFormSecond 组件。 (无论令牌是否有效)。

但是,当我访问路径“/account/reset/:token”时,我没有在屏幕上看到呈现的 PasswordResetFormSecond。但是我得到了正确的服务器响应。另外,找不到 redux store。 我做错了什么?

代码片段如下:

client/src/components/PasswordResetFormsecond.js

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useFormik } from "formik";
import * as Yup from "yup";
import {
  fetchPasswordResetMount,
  fetchPasswordResetSubmit,
} from "./stateSlices/passwordResetPasswordSlice";

const PasswordResetFormSecond = ({ history, match }) => {
  const { successMount, errorMount, successSubmit, errorSubmit } = useSelector(
    (state) => state.passwordResetPasswordStage
  );

  const dispatch = useDispatch();

  useState(() => {
    dispatch(fetchPasswordResetMount(match.params.token));
  }, []);

  const formik = useFormik({
    initialValues: {
      password: "",
      confirmPassword: "",
    },
    validationSchema: Yup.object({
      password: Yup.string().required("Please enter your password"),
      confirmPassword: Yup.string().required("Please enter your password"),
    }),
    onSubmit: async (values, { resetForm }) => {
      const { password, confirmPassword } = values;
      dispatch(
        fetchPasswordResetSubmit({
          password,
          confirmPassword,
          token: match.params.token,
        })
      );
      if (successSubmit) {
        history.push("/registerLogin");
      }
    },
  });

  let condition = successMount || errorMount;

  return (
    <div className="col-10 col-sm-8 col-md-5 mx-auto">
      {condition && (
        <div className="login-form-wrapper">
          <div className="col-10 col-sm-8 col-md-5 mx-auto">
            <h1 className="font-weight-bold">Reset Password</h1>
          </div>
          <form onSubmit={formik.handleSubmit}>
            <div className="form-group col-10 col-sm-8 col-md-5 mx-auto mt-5">
              {errorSubmit && (
                <div className="alert alert-danger" role="alert">
                  {errorSubmit}
                </div>
              )}
            </div>
            <div className="form-group col-10 col-sm-8 col-md-5 mx-auto">
              <label htmlFor="password">Password</label>
              <input
                className="form-control form-control-lg"
                id="password"
                name="password"
                type="password"
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.password}
              />
              {formik.touched.password && formik.errors.password ? (
                <small className="form-text text-danger">
                  {formik.errors.password}
                </small>
              ) : null}
            </div>
            <div className="form-group col-10 col-sm-8 col-md-5 mx-auto">
              <label htmlFor="confirmPassword">Confirm Password</label>
              <input
                className="form-control form-control-lg"
                id="confirmPassword"
                name="confirmPassword"
                type="password"
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.password}
              />
              {formik.touched.confirmPassword &&
              formik.errors.confirmPassword ? (
                <small className="form-text text-danger">
                  {formik.errors.confirmPassword}
                </small>
              ) : null}
            </div>

            <div className="col-10 col-sm-8 col-md-5 mx-auto">
              <button
                type="submit"
                className="btn btn-lg btn-primary btn-block login-button"
              >
                Reset Password
              </button>
            </div>
          </form>
        </div>
      )}
    </div>
  );
};

export default PasswordResetFormSecond;

client/src/App.js

import React, { useState } from "react";
import Header from "./components/Header";
import Home from "./components/Home";
import About from "./components/About";
import CV from "./components/CV";
import Projects from "./components/Projects";
import RegisterForm from "./components/RegisterForm";
import LoginForm from "./components/LoginForm";
import PasswordResetFormFirst from "./components/PasswordResetFormFirst";
import PasswordResetFormSecond from "./components/PasswordResetFormSecond";
import { Route, Switch } from "react-router-dom";

const App = () => {
  const [menuOpen, setMenuOpen] = useState(false);
  const handleMenuClick = () => {
    setMenuOpen(!menuOpen);
  };

  const handleOverlayClick = () => {
    setMenuOpen(!menuOpen);
  };

  const handleSidedrawerNavbarLinkClick = () => {
    setMenuOpen(!menuOpen);
  };
  return (
    <>
      <Header
        menuOpen={menuOpen}
        onMenuClick={handleMenuClick}
        onSidedrawerNavbarLinkClick={handleSidedrawerNavbarLinkClick}
        onOverlayClick={handleOverlayClick}
      />
      <Switch>
        <Route
          path="/account/reset/:token"
          component={PasswordResetFormSecond}
        />
        <Route path="/account/forgot" component={PasswordResetFormFirst} />
        <Route path="/about" component={About} />
        <Route path="/cv" component={CV} />
        <Route path="/projects" component={Projects} />
        <Route path="/registerLogin" component={LoginForm} />
        <Route path="/register" component={RegisterForm} />
        <Route path="/" exact component={Home} />
      </Switch>
    </>
  );
};

export default App;

client/src/staeSlices/passwordResetPasswordSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

const initialState = {
  user: null,
  successMount: null,
  successSubmit: null,
  errorMount: null,
  updatedUser: null,
  errorSubmit: null,
};

export const fetchPasswordResetMount = createAsyncThunk(
  "passwordReset/fetchPasswordResetMount",
  async (token, { rejectWithValue }) => {
    try {
      const { data } = await axios.get(`/account/reset/${token}`);
      return data;
    } catch (err) {
      return rejectWithValue(err.response.data);
    }
  }
);
export const fetchPasswordResetSubmit = createAsyncThunk(
  "passwordResetPassword/fetchPasswordResetInfo",
  async ({ password, confirmPassword, token }, { rejectWithValue }) => {
    try {
      const { data } = await axios.post(`/account/reset/${token}`, {
        password,
        confirmPassword,
      });
      return data;
    } catch (err) {
      return rejectWithValue(err.response.data);
    }
  }
);

export const passwordResetSlice = createSlice({
  name: "passwordReset",
  initialState,
  reducers: {},
  extraReducers: {
    [fetchPasswordResetMount.fulfilled]: (state, action) => {
      state.user = action.payload;
      state.successMount = true;
    },
    [fetchPasswordResetMount.rejected]: (state, action) => {
      state.errorMount = action.payload.message;
    },
    [fetchPasswordResetSubmit.fulfilled]: (state, action) => {
      state.updatedUser = action.payload;
      state.successSubmit = true;
    },
    [fetchPasswordResetSubmit.rejected]: (state, action) => {
      state.errorSubmit = action.payload.message;
    },
  },
});

export default passwordResetSlice.reducer;

server/routes/passwordResetRoutes.js

const express = require("express");
const crypto = require("crypto");
const asyncHandler = require("express-async-handler");
const User = require("../models/userModel");

const router = express.Router();

router.get(
  "/reset/:token",
  asyncHandler(async (req, res, next) => {
    const user = await User.findOne({
      passwordResetToken: req.params.token,
      passwordResetExpires: { $gt: Date.now() },
    });

    if (user) {
      res.json(user);
    } else {
      const err = new Error("Password reset token is invalid or has expired");
      err.status = 404;
      next(err);
    }
  })
);
router.post(
  "/reset/:token",
  asyncHandler(async (req, res, next) => {
    if (req.body.password === req.body.confirmPassword) {
      next();
    } else {
      const err = new Error("Passwords don't match.");
      err.status = 404;
      next(err);
    }
    const user = await User.findOne({
      passwordResetToken: req.params.token,
      passwordResetExpires: { $gt: Date.now() },
    });

    if (user) {
      user.password = req.body.password;
      user.passwordResetToken = undefined;
      user.passwordResetExpires = undefined;
      const updatedUser = await user.save();
      res.json(updatedUser);
    } else {
      const err = new Error("Password reset token is invalid or has expired");
      err.status = 404;
      next(err);
    }
  })
);
router.post(
  "/forgot",
  asyncHandler(async (req, res, next) => {
    const user = await User.findOne({ email: req.body.email });

    if (user) {
      user.passwordResetToken = crypto.randomBytes(20).toString("hex");
      user.passwordResetExpires = Date.now() + 3600000;
      await user.save();

      res.json({
        message: "You have been emailed a password reset link",
      });
    } else {
      const err = new Error("No account with that email exists");
      err.status = 404;
      next(err);
    }
  })
);

module.exports = router;

store.js

import { configureStore } from "@reduxjs/toolkit";
import loginReducer from "./components/stateSlices/loginSlice";
import registerReducer from "./components/stateSlices/registerSlice";
import passwordResetEmailReducer from "./components/stateSlices/passwordResetEmailSlice";
import passwordResetPasswordReducer from "./components/stateSlices/passwordResetPasswordSlice";

const loggedInUserFromStorage = localStorage.getItem("loggedInUser")
  ? JSON.parse(localStorage.getItem("loggedInUser"))
  : null;

const preloadedState = {
  login: {
    user: loggedInUserFromStorage,
  },
};

export default configureStore({
  reducer: {
    login: loginReducer,
    register: registerReducer,
    passwordResetEmail: passwordResetEmailReducer,
    passwordResetPassword: passwordResetPasswordReducer,
  },
  preloadedState,
});

GITHUB 回购:https://github.com/sundaray/password-reset

检查您的 GitHub 代码后,我发现您正在将请求代理到 /account/reset/:token,这与到 localhost:5000 的前端路由相同。一个简单的修复方法是将前端路由重命名为其他名称。例如/password/reset/:token.