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