Итак, я создал Модель, Маршруты, Контроллеры, все выглядит нормально, глядя на код. Я также создал свой редуктор, действие и компонент для внешнего интерфейса. Поскольку я пытаюсь создать продукт, он запускает действие и, кажется, успешно отправляет его, но в devtools-редуктах я вижу, что элемент, который я получаю в массиве products, имеет значение NULL. Также, когда я проверяю свою базу данных, продукт не сохраняется. Что странно, так это то, что я вижу свое изображение в папке с изображениями, которая находится на серверной части. Я не знаю, правильно ли я настроил действие, даже компонент или контроллер, возможно, маршруты. Я был бы рад, если бы кто-то мог разобраться в этом, возможно, моя реализация неверна. Я собираюсь связать свой Github и вставить некоторые коды ниже.
Кстати, я могу зарегистрироваться, войти в систему и получить пользователей, так что это что-то определенное c, чтобы сделать с этой функцией. Пожалуйста, я также рад изменить вещи в моем приложении, если вы думаете, что некоторые из моих подходов неверны, я довольно новичок в этом стеке <3 </strong>
Ссылка на Github: https://github.com/tigerabrodi/eBuy
сервер. js (Back End)
const express = require('express');
const connectDB = require('./config/db');
const uuidv4 = require("uuid/v4")
const colors = require("colors");
const morgan = require("morgan");
const multer = require("multer");
const path = require("path");
const app = express();
const PORT = process.env.PORT || 9045;
// Importing Routes
const authRoutes = require("./routes/auth");
const productRoutes = require("./routes/products");
// Connect Database
connectDB();
// Configuring Multer storage
const fileStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images');
},
filename: (req, file, cb) => {
// I used a regex to trim the name from spaces
cb(null, uuidv4() + "_" + file.originalname.replace(/\s/g, ''));
}
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
}
};
// Init Middleware
app.use(express.json({ extended: false }));
// Use Multer
app.use(
multer({ storage: fileStorage, fileFilter: fileFilter }).single('image')
);
// Serving static for images folder
app.use('/images', express.static(path.join(__dirname, 'images')));
// Dev logging middleware
if (process.env.NODE_ENV === "development") {
app.use(morgan("dev"));
}
// Define Routes
app.use('/auth', authRoutes);
app.use('/products', productRoutes);
// Serve static assets in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
app.listen(PORT, () => console.log(`Server started on port ${PORT}`.cyan.underline.bold));
маршруты продуктов (Back End)
const express = require("express");
const auth = require("../middleware/auth");
const {check} = require("express-validator");
const productController = require("../controllers/products");
const router = express.Router();
// Create Product
router.post("/", [
auth,
[
check("title", "Title is required").notEmpty(),
check("description", "Description is required").notEmpty(),
check("image", "Image is required").notEmpty(),
check("price", "Price is required").isNumeric()
]
], productController.createProduct);
module.exports = router;
Модель продукта (Back End)
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
},
{
toJSON: {virtuals: true}
})
ProductSchema.virtual("question", {
ref: "Question",
localField: "_id",
foreignField: "product"
});
module.exports = mongoose.model("Product", ProductSchema);
контроллер продукта (Back end)
const {validationResult} = require("express-validator");
const User = require("../models/User");
const Product = require("../models/Product");
// @route POST /products
// @desc Add Product
// @access Private
exports.createProduct = async (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const {title, description, price} = req.body;
if (!req.file) {
return res.status(400).json({msg: "Please upload an Image!"})
}
const image = "/" + req.file.path.replace("\\" ,"/");
const newProduct = new Product({
user: req.user.id,
title,
description,
price,
image
});
const product = await newProduct.save();
res.status(201).json(product);
console.log(product);
next();
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
next(err);
}
}
редуктор продукта (Front end)
import {ProductActionTypes} from "./product.types";
const initialState = {
products: [],
totalProducts: null,
product: null,
loading: true,
error: {}
}
const productReducer = (state = initialState, action) => {
const {payload, type} = action;
switch(type) {
case ProductActionTypes.ADD_PRODUCT:
return {
...state,
products: [payload, ...state.products],
loading: false
}
case ProductActionTypes.PRODUCT_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
export default productReducer
действия продукта (Front End)
import {ProductActionTypes} from "./product.types"
import {setAlert} from "../alert/alert.actions"
import axios from "axios"
export const addProduct = (productData, history) => async dispatch => {
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
const formData = new FormData();
formData.append("title", productData.title)
formData.append("description", productData.description)
formData.append("price", productData.price)
formData.append("image", productData.image)
try {
const res = axios.post("/products/", formData, config);
dispatch({
type: ProductActionTypes.ADD_PRODUCT,
payload: res.data
})
history.push("/dashboard");
dispatch(setAlert("Product created successfully", "success"))
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
типы продуктов (передний конец)
export const ProductActionTypes = {
ADD_PRODUCT: "ADD_PRODUCT",
PRODUCT_ERROR: "PRODUCT_ERROR"
}
Компонент продукта (передний конец)
import React, {Fragment, useState} from 'react';
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {addProduct} from '../../redux/product/product.actions';
const CreateProduct = ({history, addProduct}) => {
const [formData,
setFormData] = useState({name: "", description: "", price: 10, image: ""});
const [showImage, setShowImage] = useState(false);
const [imageName, setImageName] = useState("")
const onSubmit = e => {
e.preventDefault();
addProduct(formData, history);
}
const onChangeImage = e => {
setFormData({...formData, image: e.target.files[0]});
setShowImage(true);
setImageName(e.target.files[0].name)
}
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const {name, description, price} = formData;
return (
<Fragment>
<div className="container">
<div className="row">
<div className="col text-info font-weight-bold m-2">
*- All Fields Requried
<form onSubmit={e => onSubmit(e)}>
<div className="form-group m-2">
<label htmlFor="name">Name</label>
<input type="text" placeholder="Enter Products Name" name="name" value={name} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div className="form-group m-2">
<label htmlFor="price">Price</label>
<input type="number" name="price" placeholder="Enter Products Price" value={price} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div class="custom-file m-2">
<input type="file" onChange={e => onChangeImage(e)} class="custom-file-input bg-info" required/>
<label class="custom-file-label">{showImage ? imageName : "Upload Image"}</label>
</div>
<div className="form-group m-2">
<label htmlFor="title">Description</label>
<textarea name="description" onChange={e => onChange(e)} placeholder="Enter Products description" value={description} className="form-control" required/>
</div>
<input type="submit" value="Add Product" className="btn btn-block btn-info"/>
</form>
</div>
</div>
</div>
</Fragment>
);
}
export default connect(null, {addProduct})(withRouter(CreateProduct));