Search code examples
mongodbfile-uploadaxioscontroller

My React Form data is not saved, but only when i submit an image. Without the image it gets saved perfectly


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.


Solution

  • 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}));