I am trying to create a Game, await for that action to be fulfilled, get the id of the recently created game and dispatch it with the action to upload a photo.
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, uploadPhoto } from '../../slices/gameSlice';
import { fetchEmployees } from '../../slices/employeeSlice';
function GameForm() {
const { employees: employeesList } = useSelector((state) => state.employees);
const dispatch = useDispatch();
const [games, setGames] = useState([]);
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') {
setFormData({ ...formData, photo: e.target.files[0] }); // Update this line
} else {
setFormData({ ...formData, [e.target.name]: e.target.value });
}
};
const onSubmit = async (e) => {
e.preventDefault();
console.log('Form submitted', formData)
try {
const gameData = {
name,
description,
hours,
employees
};
const createdGame = await dispatch(createGame(gameData)).unwrap();
setGames([...games, createdGame]);
await dispatch(uploadPhoto({ id: createdGame._id, file: photo })).unwrap();
} catch (error) {
console.log(error);
}
};
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
My form has an encType=multipart/form-data
And the following is my gameSlice
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 uploadPhoto = createAsyncThunk(
'games/uploadPhoto',
async ({ id, file }, { rejectWithValue }) => {
try {
const formData = new FormData();
formData.append('file', file);
const response = await axios.put(
`http://localhost:5000/api/games/${id}/photo`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
if (!response || !response.data) {
throw new Error('Invalid response received');
}
return response.data;
} catch (error) {
return rejectWithValue(error.response?.data || error.message);
}
}
);
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 = [];
})
.addCase(uploadPhoto.pending, (state) => {
state.isLoading = true;
})
.addCase(uploadPhoto.fulfilled, (state, action) => {
state.isLoading = false;
const game = state.games.find(
(game) => game._id === action.payload._id
);
if (game) {
game.photo = action.payload.photo;
}
})
.addCase(uploadPhoto.rejected, (state) => {
state.isLoading = false;
state.games = [];
});
},
});
export const {} = gameSlice.actions;
export default gameSlice.reducer;
When i submit my form the createGame action is fullfilled and the game is created, but then the uploadPhoto is rejected. With an error message of Unexpected end of form
.
This is my method in my controller:
// @desc Upload photo to a game
// @route PUT /api/games/:id/photo
// @access Private
exports.uploadPhoto = asyncHandler(async (req, res, next) => {
const game = await Game.findById(req.params.id);
if (!game) {
return next(
new ErrorResponse(`Game not found with id of ${req.params.id}`, 404)
);
}
if (!req.files) {
return next(new ErrorResponse(`Please upload a file`, 400));
}
const file = req.files.file;
// Make sure the image is a photo
if (!file.mimetype.startsWith('image')) {
return next(new ErrorResponse(`Please upload an image file`, 400));
}
// Check filesize
if (file.size > process.env.MAX_FILE_UPLOAD) {
return next(
new ErrorResponse(
`Please upload an image less than ${process.env.MAX_FILE_UPLOAD}`,
400
)
);
}
// Create custom filename
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));
}
await Game.findByIdAndUpdate(req.params.id, { photo: file.name });
res.status(200).json({
success: true,
data: file.name,
});
});
});