0

I'm making a crud application using MERN stack to practice my skills cause i'm new to this I'm trying to create a new product with an image in it and i use multer to handle the images i did my backend well and tested it on Postman and it works fine, but when i pass the data to the frontend (React and Redux) it also works fine but instead of showing me the success or error message with toastfy, the browser redirect me to a new page with json data i sent

PS. i tried formik to handle my form state and errors and it didn't redirect me to the json page and it showed the errors messages i'm sending from the backend in the toast, but didn't work for me cause formik is really complicated working with files so i did it the simple way

  • i use multer to handle images
  • react for the frontend
  • redux as a store for react
  • react-toastify to show success and error messages

// multer middleware to handle the image

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, "./uploads/");
    },
    filename: function (req, file, cb) {
        cb(null, file.originalname);
    }
});

const fileFilter = (req, file, cb) => {
    // reject a file
    if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
        cb(null, true);
    } else {
        cb(null, false);
    }
};

const upload = multer({ storage, fileFilter });

and here's the validation and working with the backend and it's an array of methods

const createProduct = [
//validate product that it's not empthy
// then sanitize it with trima and escape
body("name")
    .isLength({ min: 5 })
    .withMessage("Must be at least 2 letters")
    .trim()
    .escape(),
body("description")
    .isLength({ min: 20 })
    .withMessage("Must be at least 10 letters")
    .trim()
    .escape(),
body("category")
    .isLength({ min: 1 })
    .withMessage("This field is required")
    .trim()
    .escape(),
body("price")
    .isLength({ min: 1 })
    .withMessage("Must be at least 1 number")
    .isNumeric()
    .withMessage("Must be Numeric")
    .trim()
    .escape(),
body("numberInStock")
    .isLength({ min: 1 })
    .withMessage("Must be at least 1 number")
    .isNumeric()
    .withMessage("Must be Numeric")
    .trim()
    .escape(),

// continue process after validation
(req, res, next) => {
    console.log(req.file);

    // get the validation errors from the request
    const errors = validationResult(req);

    // create new product after being validated and sanitized
    let newProduct = new Product({
        name: req.body.name,
        description: req.body.description,
        category: req.body.category,
        price: req.body.price,
        numberInStock: req.body.numberInStock,
        productImage: req.file.path
    });

    // if there are validation errors send them as json
    if (!errors.isEmpty()) {

        //i only show the first error
        let field = errors.errors[0].param;
        let message = errors.errors[0].msg;
        let errorMessage = field + " " + message;

        res.status(400).json({
            message: errorMessage
        });
    } else {
        newProduct.save(function (err, product) {
            if (err) {
                console.log(err);

                res.json({
                    message: "Couldn't create please try again"
                });
            } else {
                res.redirect({
                    message: "Created Successfully",
                    product
                });
            }
        });
    }
}
];

handling the route

router.post("/api/product/create",upload.single("productImage"), createProduct);

//add product action

export const addProduct = product => dispatch => {
return new Promise((resolve, reject) => {
    axios
        .post("/api/product/create", product)
        .then(res => {
            let newProduct = res.data.product;
            let successMessage = res.data.message;

            dispatch(addProductSuccess(newProduct, successMessage));
            resolve(successMessage);
        })
        .catch(error => {
            console.log(error.message);

            dispatch(addProductFailure(error));
            reject(error.message);
        });
});
};

const addProductSuccess = (product, successMessage) => {
    return {
        type: ADD_PRODUCT_SUCCESS,
        payload: {
            product,
            successMessage
        }
    };
};

const addProductFailure = error => {
    return {
        type: ADD_PRODUCT_FAILURE,
        payload: {
            error
        }
    };
};

//Add product form

function AddProductForm() {
// importing categories and laoding state from out store
const { categories, error, loading } = useSelector(
    state => state.categoriesss
);

const [state, setState] = useState({
    name: "",
    description: "",
    category: "",
    price: "",
    numberInStock: "",
    productImage: ""
});

const handleChange = e => {
    const { name, value } = e.target;
    setState(prevState => ({
        ...prevState,
        [name]: value
    }));
};

// react redux method to dispatch our functions
const dispatch = useDispatch();

// fetch all the the categories with dispatch before our component render
useEffect(() => {
    dispatch(fetchCategories());
}, [dispatch]);


// handle submit our form
const handleSubmitt = product => {

    dispatch(addProduct(product))
        .then(res => {
            toast.success(res, {
                position: toast.POSITION.BOTTOM_LEFT,
                transition: Slide
            });
        })
        .catch(err => {
            toast.error(err, {
                position: toast.POSITION.BOTTOM_LEFT,
                autoClose: false
            });
        });
};

let newProduct = {
    name: state.name,
    description: state.description,
    category: state.category,
    price: state.price,
    numberInStock: state.numberInStock,
    productImage: state.productImage
};

return (
    <Container>
        <form
            action='/api/product/create'
            method='post'
            enctype='multipart/form-data'
            onSubmit={() => handleSubmitt(newProduct)}>
            <div className='form-group'>
                <input
                    className='form-control mb-2'
                    type='text'
                    placeholder='Enter Product name'
                    name='name'
                    required
                    onChange={handleChange}
                />
            </div>
            <div className='form-group'>
                <input
                    className='form-control mb-2'
                    as='textarea'
                    placeholder='Enter Product description'
                    name='description'
                    required
                    onChange={handleChange}
                />
            </div>
            <div className='form-group'>
                <select
                    className='custom-select mb-2'
                    name='category'
                    required
                    onChange={handleChange}>
                    {loading && <option>loading...</option>}
                    {categories.map(cat => {
                        return (
                            <option key={cat._id} value={cat._id}>
                                {cat.name}
                            </option>
                        );
                    })}
                </select>
            </div>
            <div className='form-group'>
                <input
                    className='form-control mb-2'
                    type='text'
                    placeholder='Enter Product price'
                    name='price'
                    required
                    onChange={handleChange}
                />
            </div>
            <div className='form-group'>
                <input
                    className='form-control mb-2'
                    type='text'
                    placeholder='Enter Product numberInStock'
                    name='numberInStock'
                    required
                    onChange={handleChange}
                />
            </div>
            <div className='form-group'>
                <input
                    className='custom custom-file mb-2'
                    type='file'
                    name='productImage'
                    required
                    onChange={handleChange}
                />
            </div>
            <Button
                variant='primary'
                type='submit'
                onClick={() => handleSubmitt(newProduct)}>
                Submit{" "}
            </Button>{" "}
        </form>
    </Container>
    );
}

export default AddProductForm;

results after the request is done with success

results after the request is done with error

hamohuh
  • 83
  • 3
  • 10

1 Answers1

0

try this

<form
    action='/api/product/create'
    method='post'
    enctype='multipart/form-data'
    onSubmit={(e) => {
        handleSubmitt(newProduct)
        e.preventDefault()
}>
Rin
  • 66
  • 4
  • omg thank you so much, do you have any idea why multer always give me an error like "Cannot read property 'path' of undefined" and it's like "C:\\fakepath\\img.jpg" – hamohuh Jun 10 '20 at 01:49
  • @hamohuh well, i have no idea. But, maybe [this](https://stackoverflow.com/questions/49208813/) related with your problem – Rin Jun 10 '20 at 02:18
  • thank you bro and [This one](https://stackoverflow.com/questions/53938783/how-to-pass-an-image-to-the-backend-from-react-and-redux) was the solution so i was sending application/json and i'm supposed to be sending multipart/form-data – hamohuh Jun 10 '20 at 10:17