在 youtube 上从 EdRoh 构建 MERN 项目时,我遇到了这个问题,在注册页面上,当我提交表单数据时,它应该重定向到登录页面,并且数据应该存储在 mongodb 数据库中。 当我用 Postman 检查它时,它工作正常,我可以将数据发布到数据库。 但是,当我尝试通过注册页面上的前端进行注册时,我的页面没有重定向,我的服务器也没有响应任何错误或成功消息。我已经安装了 Morgan,它使用 CRUD 方法记录 http 状态代码。但它没有记录任何东西。我还安装了cors。 我使用 multer 来存储数据,使用 JWT 在后端进行令牌验证,并使用 formik 作为表单,是的,通过我构建的 api 将数据发送到我的服务器。
我正在链接屏幕截图和代码,我认为这对任何人都可能有用,可以帮助我。
这是 form.jsx 文件,其中有两个有条件渲染的表单组件。
import { useState } from 'react';
import {
Box,
Button,
TextField,
useMediaQuery,
Typography,
useTheme,
} from '@mui/material';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import { Formik } from 'formik';
import * as yup from 'yup';
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { setLogin } from 'state';
import Dropzone from 'react-dropzone';
import FlexBetween from 'components/FlexBetween';
const registerSchema = yup.object().shape({
firstName: yup.string().required('required'),
lastName: yup.string().required('required'),
email: yup.string().email('invalid email').required('required'),
password: yup.string().required('required'),
location: yup.string().required('required'),
occupation: yup.string().required('required'),
picture: yup.string().required('required'),
});
const loginSchema = yup.object().shape({
email: yup.string().email('invalid email').required('required'),
password: yup.string().required('required'),
});
const initialValuesRegister = {
firstName: '',
lastName: '',
email: '',
password: '',
location: '',
occupation: '',
picture: '',
};
const initialValuesLogin = {
email: '',
password: '',
};
const Form = () => {
const [pageType, setPageType] = useState('login');
const { palette } = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
const isNonMobile = useMediaQuery('(min-width:600px)');
const isLogin = pageType === 'login';
const isRegister = pageType === 'register';
const register = async (values, onSubmitProps) => {
// this allows us to send form info with image
const formData = new FormData();
for (let value in values) {
formData.append(value, values[value]);
}
formData.append('picturePath', values.picture.name);
const savedUserResponse = await fetch(
'http://localhost:3001/auth/register',
{
method: 'POST',
body: formData,
}
);
const savedUser = await savedUserResponse.json();
onSubmitProps.resetForm();
if (savedUser) {
setPageType('login');
}
};
const login = async (values, onSubmitProps) => {
const loggedInResponse = await fetch('http://localhost:3001/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
const loggedIn = await loggedInResponse.json();
onSubmitProps.resetForm();
if (loggedIn) {
dispatch(
setLogin({
user: loggedIn.user,
token: loggedIn.token,
})
);
navigate('/home');
}
};
const handleFormSubmit = async (values, onSubmitProps) => {
if (isLogin) await login(values, onSubmitProps);
if (isRegister) await register(values, onSubmitProps);
};
return (
<Formik
onSubmit={handleFormSubmit}
initialValues={isLogin ? initialValuesLogin : initialValuesRegister}
validationSchema={isLogin ? loginSchema : registerSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
setFieldValue,
resetForm,
}) => (
<form onSubmit={handleSubmit}>
<Box
display='grid'
gap='30px'
gridTemplateColumns='repeat(4, minmax(0, 1fr))'
sx={{
'& > div': { gridColumn: isNonMobile ? undefined : 'span 4' },
}}
>
{isRegister && (
<>
<TextField
label='First Name'
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
name='firstName'
error={
Boolean(touched.firstName) && Boolean(errors.firstName)
}
helperText={touched.firstName && errors.firstName}
sx={{ gridColumn: 'span 2' }}
/>
<TextField
label='Last Name'
onBlur={handleBlur}
onChange={handleChange}
value={values.lastName}
name='lastName'
error={Boolean(touched.lastName) && Boolean(errors.lastName)}
helperText={touched.lastName && errors.lastName}
sx={{ gridColumn: 'span 2' }}
/>
<TextField
label='Location'
onBlur={handleBlur}
onChange={handleChange}
value={values.location}
name='location'
error={Boolean(touched.location) && Boolean(errors.location)}
helperText={touched.location && errors.location}
sx={{ gridColumn: 'span 4' }}
/>
<TextField
label='Occupation'
onBlur={handleBlur}
onChange={handleChange}
value={values.occupation}
name='occupation'
error={
Boolean(touched.occupation) && Boolean(errors.occupation)
}
helperText={touched.occupation && errors.occupation}
sx={{ gridColumn: 'span 4' }}
/>
<Box
gridColumn='span 4'
border={`1px solid ${palette.neutral.medium}`}
borderRadius='5px'
p='1rem'
>
<Dropzone
acceptedFiles='.jpg,.jpeg,.png'
multiple={false}
onDrop={(acceptedFiles) =>
setFieldValue('picture', acceptedFiles[0])
}
>
{({ getRootProps, getInputProps }) => (
<Box
{...getRootProps()}
border={`2px dashed ${palette.primary.main}`}
p='1rem'
sx={{ '&:hover': { cursor: 'pointer' } }}
>
<input {...getInputProps()} />
{!values.picture ? (
<p>Add Picture Here</p>
) : (
<FlexBetween>
<Typography>{values.picture.name}</Typography>
<EditOutlinedIcon />
</FlexBetween>
)}
</Box>
)}
</Dropzone>
</Box>
</>
)}
<TextField
label='Email'
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
name='email'
error={Boolean(touched.email) && Boolean(errors.email)}
helperText={touched.email && errors.email}
sx={{ gridColumn: 'span 4' }}
/>
<TextField
label='Password'
type='password'
onBlur={handleBlur}
onChange={handleChange}
value={values.password}
name='password'
error={Boolean(touched.password) && Boolean(errors.password)}
helperText={touched.password && errors.password}
sx={{ gridColumn: 'span 4' }}
/>
</Box>
{/* BUTTONS */}
<Box>
<Button
fullWidth
type='submit'
sx={{
m: '2rem 0',
p: '1rem',
backgroundColor: palette.primary.main,
color: palette.background.alt,
'&:hover': { color: palette.primary.main },
}}
>
{isLogin ? 'LOGIN' : 'REGISTER'}
</Button>
<Typography
onClick={() => {
setPageType(isLogin ? 'register' : 'login');
resetForm();
}}
sx={{
textDecoration: 'underline',
color: palette.primary.main,
'&:hover': {
cursor: 'pointer',
color: palette.primary.light,
},
}}
>
{isLogin
? "Don't have an account? Sign Up here."
: 'Already have an account? Login here.'}
</Typography>
</Box>
</form>
)}
</Formik>
);
};
export default Form;
这是app.js文件
import { BrowserRouter, Navigate, Routes, Route } from 'react-router-dom';
import Homepage from 'scenes/homePage';
import LoginPage from 'scenes/loginPage';
import ProfilePage from 'scenes/profilePage';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { createTheme } from '@mui/material/styles';
import { themeSettings } from './theme';
function App() {
const mode = useSelector((state) => state.mode);
const theme = useMemo(() => createTheme(themeSettings(mode)), [mode]);
const isAuth = Boolean(useSelector((state) => state.token));
return (
<div className='app'>
<BrowserRouter>
<ThemeProvider theme={theme}>
<CssBaseline />
<Routes>
<Route
path='/'
element={<LoginPage />}
/>
<Route
path='/home'
element={isAuth ? <Homepage /> : <Navigate to='/' />}
/>
<Route
path='/profile/:userId'
element={isAuth ? <ProfilePage /> : <Navigate to='/' />}
/>
</Routes>
</ThemeProvider>
</BrowserRouter>
</div>
);
}
export default App;
这是后端的身份验证控制器逻辑。
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import User from '../models/User.js';
/* REGISTER USER */
export const register = async (req, res) => {
try {
const {
firstName,
lastName,
email,
password,
picturePath,
friends,
location,
occupation,
} = req.body;
const salt = await bcrypt.genSalt();
const passwordHash = await bcrypt.hash(password, salt);
const newUser = new User({
firstName,
lastName,
email,
password: passwordHash,
picturePath,
friends,
location,
occupation,
viewedProfile: Math.floor(Math.random() * 10000),
impressions: Math.floor(Math.random() * 10000),
});
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (err) {
res.status(500).json({ error: err.message });
}
};
/* LOGGING IN */
export const login = async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email: email });
if (!user) return res.status(400).json({ msg: 'User does not exist. ' });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ msg: 'Invalid credentials. ' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
delete user.password;
res.status(200).json({ token, user });
} catch (err) {
res.status(500).json({ error: err.message });
}
};
这是后端的index.js文件
import express from 'express';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';
import multer from 'multer';
import helmet from 'helmet';
import morgan from 'morgan';
import path from 'path';
import { fileURLToPath } from 'url';
import authRoutes from './routes/auth.js';
import userRoutes from './routes/users.js';
import postRoutes from './routes/posts.js';
import { register } from './controllers/auth.js';
import { createPost } from './controllers/posts.js';
import { verifyToken } from './middleware/auth.js';
import User from './models/User.js';
import Post from './models/Post.js';
import { users, posts } from './data/index.js';
/* CONFIGURATIONS */
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config();
const app = express();
app.use(express.json());
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }));
app.use(morgan('common'));
app.use(bodyParser.json({ limit: '30mb', extended: true }));
app.use(bodyParser.urlencoded({ limit: '30mb', extended: true }));
app.use(cors());
app.use('/assets', express.static(path.join(__dirname, 'public/assets')));
/* FILE STORAGE */
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public/assets');
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage });
/* ROUTES WITH FILES */
app.post('/auth/register', upload.single('picture'), register);
app.post('/posts', verifyToken, upload.single('picture'), createPost);
/* ROUTES */
app.use('/auth', authRoutes);
app.use('/users', userRoutes);
app.use('/posts', postRoutes);
/* MONGOOSE SETUP */
const PORT = process.env.PORT || 6001;
mongoose
.connect(process.env.MONGO_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
app.listen(PORT, () => console.log(`Server Port: ${PORT}`));
/* ADD DATA ONE TIME */
// User.insertMany(users);
// Post.insertMany(posts);
})
.catch((error) => console.log(`${error} did not connect`));
我尝试使用前端将数据发送到我的服务器,服务器应该响应 201 状态代码,并且我的数据应该已插入到我的数据库中。
我期望当我的用户注册时,他们将被重定向到登录页面,登录后他们应该看到主页。
感谢您提出问题。我已将您的代码与教程中的原始代码进行了比较,一切似乎都是相同的。我还完成了这个很棒的教程,并在我自己的项目中测试了注册行为。
这是我注意到的:没有个人资料图片似乎无法注册。在您的屏幕截图中,我可以清楚地看到没有提供图片。但是,在您的
registerSchema
中,picture
字段被标记为必填。
const registerSchema = yup.object().shape({
firstName: yup.string().required('required'),
lastName: yup.string().required('required'),
email: yup.string().email('invalid email').required('required'),
password: yup.string().required('required'),
location: yup.string().required('required'),
occupation: yup.string().required('required'),
picture: yup.string().required('required'), <<<< !HERE!
});
要解决此问题,请尝试在前端注册时选择图像。
我希望这有帮助
即使在前端注册时选择了图像,它也不起作用