问题描述
我正在MERN应用程序中实现密码重置功能.当用户输入要重设密码的电子邮件地址时,他们会在邮件中获得一个休息密码链接.现在,当他们访问该链接时,他们应该看到在屏幕上呈现的PasswordResetFormSecond组件.(无论令牌是否有效).
I am implementing a password reset functionality in a MERN app. When a user enters the email address for which they want to reset the password, they get a rest password link in their mail. Now, when they visit that link, they should see the PasswordResetFormSecond component rendered on the screen. (irrespective of whether the token is valid or not).
但是,当我访问路径"/account/reset/:token"时,我看不到屏幕上呈现了PasswordResetFormSecond.但是,我得到了正确的服务器响应.另外,找不到redux存储.我在做什么错了?
However, when I am visiting the path "/account/reset/:token", I don't see the PasswordResetFormSecond being rendered on the screen. I get the correct server responses however. Also, the redux store is not found.What am I doing wrong?
代码段如下:
client/src/components/PasswordResetFormsecond.js
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
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
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
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 REPO: https://github.com/sundaray/password-reset
GITHUB REPO: https://github.com/sundaray/password-reset
推荐答案
检查您的GitHub代码后,我看到您正在将请求代理到/account/reset/:token
到 localhost:5000
的前端路由.一个简单的解决方法是将前端路由重命名为其他路由.例如/password/reset/:token
.
Upon inspecting your GitHub code, I see you're proxying requests to /account/reset/:token
which is the same as the frontend route to localhost:5000
. A simple fix would be to rename the frontend route to something else. e.g. /password/reset/:token
.
这篇关于找不到redux存储并且无法在React中为特定路径呈现组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!