【发布时间】:2019-10-20 10:59:09
【问题描述】:
我会尝试给出一个前提,然后用代码跟进它。
我决定在我的 React 项目中实现 Material UI,并且我完成了大部分工作。应用程序是如何设置的,用户将面临登录页面。 Login.js 模块呈现SignIn.js module,输入他们的凭据,然后单击提交。 formData、onChange 和 onSubmit 作为 props 从 Login 组件传递给 SignIn 组件 - Login 组件通过 mapStateToProps 接收这些。 Login 组件使用 connect 中间件将 redux 状态链接到 react 应用程序。
单击提交会触发 formData(在传递给 SignIn 组件的 Login 组件中)点击位于 "../../actions/auth"; 中的我的 login 方法。错误发生在此方法内部,在 try catch 中的 axios 调用中,我尝试与后端通信const response = await axios.post("/api/auth", body, config);
奇怪的是dispatch({ type: LOGIN_SUCCESS, payload: response.data }); 从未被命中,这应该将状态设置为从后端返回的令牌,因为似乎LOGIN_SUCCESS 从未被执行。但非常奇怪的是,控制台记录令牌确实有效!似乎它从未被存储,而是强制 AUTH_ERROR 被调用。
这是我的登录组件:
// Login.js
import SignIn from "../../material/SignIn";
const Login = ({ setAlert, login, isAuthenticated }) => {
const [formData, setFormData] = useState({
email: "",
password: ""
});
const { email, password } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
login(email, password);
};
// Redirect if logged in
if (isAuthenticated) {
return <Redirect to="/dashboard" />;
}
return (
<Fragment>
<SignIn
email={email}
password={password}
onSubmit={onSubmit}
onChange={onChange}
isAuthenticated={isAuthenticated}
/>
</Fragment>
);
};
Login.propTypes = {
setAlert: PropTypes.func.isRequired,
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
export default connect(
mapStateToProps,
{ setAlert, login }
)(Login);
它正在渲染的 SignIn 组件在这里:
// SignIn.js
export default function SignIn({ email, password, onChange, onSubmit }) {
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form onSubmit={e => onSubmit(e)} className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
onChange={e => onChange(e)}
fullWidth
id="email"
label="Email Address"
name="email"
value={email}
// autoComplete="email"
autoFocus
/>
<TextField
variant="outlined"
margin="normal"
required
onChange={e => onChange(e)}
fullWidth
name="password"
label="Password"
type="password"
value={password}
id="password"
autoComplete="current-password"
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
<Box mt={5}>
<MadeWithLove />
</Box>
</Container>
);
}
单击提交按钮会在我的登录组件中引发onSubmit 方法:
// Login user
export const login = (email, password) => async dispatch => {
// Config needed because we're sending data
const config = {
headers: {
"Content-Type": "application/json"
}
};
const body = JSON.stringify({ email, password });
try {
const response = await axios.post("/api/auth", body, config);
// Skips over this dispatch
dispatch({
type: LOGIN_SUCCESS,
payload: response.data
});
// But hits this dispatch.. and then console logs 'REACHED' as seen below
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => {
dispatch(setAlert(error.msg, "danger"));
});
}
dispatch({
type: LOGIN_FAIL
});
}
};
如果你注意到,在 axios 调用之后,loadUser is called,定义为:
// Load user
export const loadUser = () => async dispatch => {
const token = localStorage.token;
console.log('REACHED!'); // reached
if (token) {
setAuthToken(token);
}
try {
const response = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: response.data
});
} catch (err) {
dispatch({
type: AUTH_ERROR // This is dispatched
});
}
};
后端路由如下:
// @route POST api/auth
// @desc Authenticate user and get token
// @access Public
router.post(
"/",
[
check("email", "Please include a valid email").isEmail(),
check("password", "Please is required").exists()
],
async (req, res) => {
const errors = validationResult(req);
// send back any errors
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const { email, password } = req.body;
try {
// check if user exists, send error if so
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ errors: [{ msg: "Invalid credentials" }] });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res
.status(400)
.json({ errors: [{ msg: "Invalid credentials" }] });
}
// return jsonwebtoken so that way they're logged in right
// away, without having to log in after registering
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: process.env.PORT ? 3600 : 36000
},
(err, token) => {
if (err) throw err;
console.log(token); // prints token!
return res.json({ token });
}
);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
}
);
在这一点上我很困惑。令牌正在呈现,但似乎 React 并没有在 Node 有机会将其发回之前“等待”响应。
【问题讨论】:
-
你用的是什么中间件?它需要支持异步操作。
-
您说“发生错误...”但不是很清楚在哪里以及发生了什么错误。是 AJAX 请求中的错误吗?还是 JS 异常? “记录令牌确实有效”您在哪里尝试记录令牌?
-
@EricHasselbring 在我决定尝试实现 Material UI 之前,一切正常。
-
@ThiagoBarcala 在我在问题中发布的最后一个函数中,在 JWT.sign 中的
POST api/auth中。似乎令牌是在后端创建的,但没有在前端的const response = await..中返回
标签: node.js reactjs redux react-redux