so i have a react app with front-end and a back-end, in the front-end amongst other things i have a form that allows users to create a post. after collecting the form data i add the Auth Token using redux and pass it to the backend api usign axios where it gets saved to the mongoDB.
If i submit a form without an image its gets saved to the database nicely, but when i try to do the same with an image, it doesnt save the post. Note that the image should be saved to the database as a base64 object.
here are the files: NewPost.jsx:
import React from "react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import Spinner from "../../components/Spinner";
import { toast } from "react-toastify";
import SideNav from "../../components/navigation/SideNav";
import FileBase from "react-file-base64";
import FormInput from "../../components/forms/FormInput";
import FormButton from "../../components/forms/FormButton";
import Editor from "../../components/forms/Editor";
import { createPost, reset } from "../../features/post/postSlice";
const Posts = () => {
const [formData, setFormData] = useState({
title: "",
description: "",
body: "",
image: "",
});
const { title, description, body, image } = formData;
const navigate = useNavigate();
const dispatch = useDispatch();
const { posts, isLoading, isError, isSuccess, message } = useSelector(
(state) => state.posts
);
useEffect(() => {
if (isError) {
toast.error(message);
}
if (isSuccess) {
navigate("/posts");
}
dispatch(reset());
}, [posts, isError, isSuccess, message, navigate, dispatch]);
const onSubmit = (e) => {
e.preventDefault();
const postData = {
title,
description,
body,
image,
};
console.log("page")
console.log(postData)
dispatch(createPost(postData));
};
if (isLoading) {
return <Spinner />;
}
return (
<div className="flex text-sm h-screen">
<SideNav />
{/* Main Content */}
<div className="w-3/6 bg-gray-400 shadow-2xl rounded-lg mx-auto text-center py-12 mt-2 rounded-xl">
<div className="flex items-center justify-center">
<div className="text-3xl text-gray-900">New Post</div>
</div>
{/* main */}
<div className="p-10 flex flex-col justify-center">
<form onSubmit={onSubmit}>
<div className="mb-4">
{/* tilte */}
<FormInput
label="Title"
name="title"
type="text"
value={title}
placeholder="Post title"
onChange={(e) =>
setFormData({ ...formData, title: e.target.value })
}
required={true}
/>
</div>
<div className="mb-4">
{/* Cover image */}
<FileBase
name="image"
type="file"
multiple={false}
onDone={({ base64 }) =>
setFormData({ ...formData, image: base64 })
}
/>
</div>
<div className="mb-4">
{/* Description */}
<FormInput
label="Decription"
name="description"
type="text"
value={description}
placeholder="Short Description"
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
required={true}
/>
</div>
<div className="mb-6">
<Editor
name="body"
label="Body"
rows="10"
placeholder="Whats On your mind"
value={body}
onChange={(e) =>
setFormData({ ...formData, body: e.target.value })
}
/>
</div>
{/* submit button */}
<div className="m-3 flex items-center justify-between">
<FormButton type="submit" text="Post" />
</div>
</form>
</div>
</div>
</div>
);
};
export default Posts;
postSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import postService from "./postService";
const initialState = {
posts: [],
isError: false,
isSuccess: false,
isLoading: false,
message: "",
};
export const createPost = createAsyncThunk(
"posts/",
async (postData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
console.log("Slice")
console.log(token)
console.log(postData)
return await postService.createPost(postData, token);
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
export const postSlice = createSlice({
name: "post",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers: (builder) => {
builder
.addCase(createPost.pending, (state) => {
state.isLoading = true;
})
.addCase(createPost.fulfilled, (state) => {
state.isLoading = false;
state.isSuccess = true;
state.posts.push();
})
.addCase(createPost.rejected, (state, action) => {
state.isLoading = false;
state.isError = true;
state.message = action.payload;
})
.addCase(getPosts.pending, (state) => {
state.isLoading = true;
});
},
});
export const { reset } = postSlice.actions;
export default postSlice.reducer;
postService.js
import axios from "axios";
const API_URL = "http://localhost:5000/api/posts/";
const createPost = async (postData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
console.log("service")
console.log(config)
console.log(postData)
const response = await axios.post(API_URL, postData, config);
return response.data;
};
const postService = {
createPost,
};
export default postService;
postController.js
const asyncHandler = require('express-async-handler');
const Post = require('../models/postModel');
const User = require('../models/userModel');
// @desc create a post
// @route POST /api/posts/
// @access Private
const createPost = asyncHandler(async (req,res) => {
console.log("controller")
console.log(req.body)
if(!req.body.body){
res.status(400);
throw new Error('Please fill in all fields');
}
const post = await Post.create({
user: req.user.id,
title: req.body.title,
description: req.body.description,
body: req.body.body,
image: req.body.image
});
res.status(200).json({message: 'Post created', post: post});
});
module.exports = {
createPost,
}
Since there are no errors, i tried console.logging the data at the various functions, and when it has an image only the controller doesn`t log the data but i still dont know why.
So I finally figured it out. While trying to resolve my issue, I started console logging everything, and I realized that when my form had an image, the server would reject it because the express file limit was set too low (it was the default value).
The reason i didn't realize this was that when it rejected the request it still sent back a response with status 200
"data": {
"message": "request entity too large",
"stack": "PayloadTooLargeError: request entity too large\n at readStream (/home/kingston/Documents/ict_society/ictsociety/node_modules/raw-body/index.js:156:17)\n at getRawBody (/home/kingston/Documents/ict_society/ictsociety/node_modules/raw-body/index.js:109:12)\n at read (/home/kingston/Documents/ict_society/ictsociety/node_modules/body-parser/lib/read.js:79:3)\n at jsonParser (/home/kingston/Documents/ict_society/ictsociety/node_modules/body-parser/lib/types/json.js:135:5)\n at Layer.handle [as handle_request] (/home/kingston/Documents/ict_society/ictsociety/node_modules/express/lib/router/layer.js:95:5)\n at trim_prefix (/home/kingston/Documents/ict_society/ictsociety/node_modules/express/lib/router/index.js:328:13)\n at /home/kingston/Documents/ict_society/ictsociety/node_modules/express/lib/router/index.js:286:9\n at Function.process_params (/home/kingston/Documents/ict_society/ictsociety/node_modules/express/lib/router/index.js:346:12)\n at next (/home/kingston/Documents/ict_society/ictsociety/node_modules/express/lib/router/index.js:280:10)\n at cors (/home/kingston/Documents/ict_society/ictsociety/node_modules/cors/lib/index.js:188:7)"
},
"status": 200,
"statusText": "OK",
"headers": {
"content-length": "1231",
"content-type": "application/json; charset=utf-8"
},
Anyway, from there, I just raised the file limit on my server.js like so and i was golden. :)
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb',extended: false}));