0

I am doing an app with express, mongodb and reactjs. In my API i have a method to create a Game. A Game has the following structure:

"name": "Hit the Jackpot",
"description": "Test your luck and win fabulous prizes in this carnival game sensation.",
"employees": ["5d713995b721c3bb38c1f5d9"],
"hours": [
    {
      "opening": "2023-06-07T10:00:00Z",
      "closing": "2023-06-07T20:00:00Z"
    },
    
    ],
"createdAt": "2023-06-07T14:00:00Z"
   },

I am using multer to upload files and store them.

This is my games.js (controller)

const Game = require('../models/Game');
const Employee = require('../models/Employee');
const asyncHandler = require('../middleware/async');
const ErrorResponse = require('../utils/errorResponse');
const path = require('path');
const multer = require('multer');

// @desc   Create a game
// @route  POST /api/games/register
// @access Private

exports.registerGame = asyncHandler(async (req, res, next) => {
  const { name, description, employees, hours } = req.body;

  // Verify that the hours are valid and dont overlap
  const overlappingHours = hours.some((currentHour, currentIndex) => {
    return hours.slice(currentIndex + 1).some((nextHour) => {
      return (
        (currentHour.opening <= nextHour.opening &&
          nextHour.opening < currentHour.closing) ||
        (currentHour.opening < nextHour.closing &&
          nextHour.closing <= currentHour.closing) ||
        (nextHour.opening <= currentHour.opening &&
          currentHour.closing <= nextHour.closing)
      );
    });
  });

  if (overlappingHours) {
    return res.status(400).json({
      success: false,
      error: 'Range of hours are overlapping',
    });
  }

  upload(req, res, async (err) => {
    console.log('req.file', req.file);
    console.log('err', err);
    if (err) {
      return next(new ErrorResponse('Problem with file upload', 500));
    }

    if (!req.file) {
      return next(new ErrorResponse('Please upload a file', 400));
    }

    //Custome file name
    file.name = `photo_${game._id}${path.parse(file.name).ext}`;

    file.mv(`${process.env.FILE_UPLOAD_PATH}/${file.name}`, async (err) => {
      if (err) {
        console.error(err);
        return next(new ErrorResponse(`Problem with file upload`, 500));
      }
    });

    const game = await Game.create({
      name,
      description,
      employees,
      hours,
      photo: file.name,
    });

    res.status(201).json({
      success: true,
      data: game,
    });
  });
});

exports.getGames = asyncHandler(async (req, res) => {
  const games = await Game.find({});

  res.status(200).json({
    success: true,
    count: games.length,
    data: games,
  });
});

//MULTER file upload

// Set storage engine
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, process.env.FILE_UPLOAD_PATH); // Set the destination folder for uploaded files
  },
  filename: function (req, file, cb) {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    cb(
      null,
      `photo_${game._id}_${uniqueSuffix}${path.extname(file.originalname)}`
    ); // Generate a unique filename for the uploaded file
  },
});

// Create multer instance
const upload = multer({
  storage: storage,
  limits: {
    fileSize: process.env.MAX_FILE_UPLOAD,
  },
  fileFilter: function (req, file, cb) {
    if (file.mimetype.startsWith('image')) {
      cb(null, true); // Accept the file if it is an image
    } else {
      cb(new Error('Please upload an image file')); // Reject the file if it is not an image
    }
  },
}).single('file');

And this is my form component GameForm.js

import React, { useState, useEffect } from 'react'
import FormGroup from '../UI/FormGroup'
import '../../styles/components/Auth/RegisterForm.css'
import { useDispatch, useSelector } from 'react-redux';
import { createGame } from '../../slices/gameSlice';
import { fetchEmployees } from '../../slices/employeeSlice';

function GameForm() {

  const { employees: employeesList } = useSelector((state) => state.employees);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchEmployees());
  }, [dispatch]);

    const [formData, setFormData] = useState({
        name: "",
        description: "",
        employees: [],
        hours: [
          {
            opening: "",
            closing: ""
          }
        ],
        photo:null
      });
    
      const { name, description,  hours, employees, photo } = formData;
    
      const onChange = e => {
        if (e.target.name === 'opening' || e.target.name === 'closing') {
          setFormData({
            ...formData,
            hours: [
              {
                ...hours[0],
                [e.target.name]: e.target.value
              }
            ]
          });
        } else if (e.target.name === 'employees') {
          const selectedOptions = Array.from(e.target.options)
          .filter((option) => option.selected)
          .map((option) => option.value);
          setFormData({ ...formData, [e.target.name]: selectedOptions });
        } else if (e.target.name === 'photo') {
          const file = e.target.files[0];
          setFormData({ ...formData, [e.target.name]: file });
        } else { 
          setFormData({ ...formData, [e.target.name]: e.target.value });
        }
      };

      const onSubmit = e => {
        e.preventDefault();
        dispatch(createGame(formData));
      }
      

    const formFields = [
        {
          name: "name",
          type: "text",
          placeholder: "Name",
          label: "Name",
          value: name,
          onChange: onChange
        },
        {
          name: "description",
          type: "text",
          placeholder: "Description",
          label: "Description",
          value: description,
          onChange: onChange
        },
        {
            name: "photo",
            type: "file",
            placeholder: "Game Photo",
            label: "Game Photo",
            onChange: onChange
        },
        {
         name: "opening",
         type: "datetime-local",
         placeholder: "Opening Hour",
         label: "Opening Hour",
         value: hours[0].opening,
         onChange: onChange
        },
        {
          name: "closing",
          type: "datetime-local",
          placeholder: "Closing Hour",
          label: "Closing Hour",
          value: hours[0].closing,
          onChange: onChange
         },
         {
          name: "employees",
          type: "select",
          placeholder: "Employees",
          label: "Employees",
          value: employees,
          options: employeesList.filter(employee => employee.type === 'manager').map((employee) => ({
            value: employee._id,
            label: `${employee.name} ${employee.lastName}`,
          })),
          multiple: true,
          onChange: onChange
        },
      ]
      
      console.log(formData)
      
  return (
    <div style={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', width: '100%'}}>
      <form className='form_container' onSubmit={onSubmit} encType='multipart/form-data' >
          {formFields.map((field) => (
              <FormGroup key={field.name} {...field} multiple={field.multiple} />
          ))}
          <button type="submit">Register</button>
      </form>
    </div>
  )
}

export default GameForm

Where i send all the data as formData and receive it in my slice.

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

const initialState = {
  games: [],
  isLoading: false,
};

export const createGame = createAsyncThunk(
  'games/createGame',
  async (game, { rejectWithValue }) => {
    try {
      const config = {
        headers: {
          'Content-Type': 'application/json',
        },
      };
      const body = JSON.stringify(game);
      const response = await axios.post(
        'http://localhost:5000/api/games/register',
        body,
        config
      );
      return response.data.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

export const getGames = createAsyncThunk(
  'games/getGames',
  async (_, { rejectWithValue }) => {
    try {
      const response = await axios.get('http://localhost:5000/api/games');
      console.log(response.data);
      return response.data.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

export const gameSlice = createSlice({
  name: 'games',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(getGames.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(getGames.fulfilled, (state, action) => {
        state.isLoading = false;
        state.games = action.payload;
      })
      .addCase(getGames.rejected, (state) => {
        state.isLoading = false;
        state.games = [];
      })
      .addCase(createGame.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(createGame.fulfilled, (state, action) => {
        state.isLoading = false;
        state.games.push(action.payload);
      })
      .addCase(createGame.rejected, (state) => {
        state.isLoading = false;
        state.games = [];
      });
  },
});

export const {} = gameSlice.actions;

export default gameSlice.reducer;

The issue is that i am getting a 404 bad request with an error message of Please upload a file

I console log my formData before i submit the form and this i what i am sending

{name: 'sadsads', description: 'asdsadsa', employees: Array(1), hours: Array(1), photo: File}
description
: 
"asdsadsa"
employees
: 
['5d713995b721c3bb38c1f5d6']
hours
: 
[{…}]
name
: 
"sadsads"
photo
: 
File {name: 'pexels-adrian-gabriel-1113927.jpg', lastModified: 1686791592156, lastModifiedDate: Wed Jun 14 2023 22:13:12 GMT-0300 (hora estándar de Argentina), webkitRelativePath: '', size: 2633645, …}
[[Prototype]]
: 
Object

So i am sending a file with the name.

But my redux action createGame comes rejected

type(pin):"games/createGame/rejected"
success(pin):false
error(pin):"Please upload a file"
name(pin):"sadsads"
description(pin):"asdsadsa"
0(pin):"5d713995b721c3bb38c1f5d6"
photo(pin):
requestId(pin):"ders8C0k4SG1FZ9jzn0CR"
rejectedWithValue(pin):true
requestStatus(pin):"rejected"
aborted(pin):false
condition(pin):false

And this is my formdata that i am sending

{
    "name": "sadsads",
    "description": "asdsadsa",
    "employees": [
        "5d713995b721c3bb38c1f5d6"
    ],
    "hours": [
        {
            "opening": "2023-06-01T23:12",
            "closing": "2023-06-09T23:12"
        }
    ],
    "photo": {}
}

So my photo is coming as an empty object. What am i doing wrong? It is probably an issue in my slice because the formData contains a file name, but then in my redux devtools my photo is empty so it seems i am not sending anything to my backend.

tukitooo1998
  • 337
  • 10
  • To send files from client to server, you need to use a multipart request. I think this might help you out: https://stackoverflow.com/a/43014086/18958751 – gawlster Jun 17 '23 at 03:19

0 Answers0