here in this code I am able to upload only a single image and upload it to cloudinary api and it works fine, the PROBLEM is it keeps only sending only a single image even though i've specified multiple in the input tag and it loops after it has the event target files and append them to the formData, while what I want is to upload as many images as I want and I have the mongodb model image field as an array. so backend is very solid and doesn't have a problem. the issue is on the front end not being able to send an array of images. it only sends one. i have removed the react hook form validation just incase to see if it works
import axios from 'axios';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useReducer, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { getError } from '../../utils/error';
import { tokens } from '../../utils/theme';
import {
Grid,
Box,
List,
ListItem,
Typography,
Card,
Button,
ListItemText,
TextField,
useTheme,
CircularProgress,
FormControlLabel,
Checkbox,
} from '@mui/material';
import Header from '../../components/Header';
import Topbar from '../../components/global/Topbar';
function reducer(state, action) {
switch (action.type) {
case 'FETCH_REQUEST':
return { ...state, loading: true, error: '' };
case 'FETCH_SUCCESS':
return { ...state, loading: false, error: '' };
case 'FETCH_FAIL':
return { ...state, loading: false, error: action.payload };
case 'UPDATE_REQUEST':
return { ...state, loadingUpdate: true, errorUpdate: '' };
case 'UPDATE_SUCCESS':
return { ...state, loadingUpdate: false, errorUpdate: '' };
case 'UPDATE_FAIL':
return { ...state, loadingUpdate: false, errorUpdate: action.payload };
case 'UPLOAD_REQUEST':
return { ...state, loadingUpload: true, errorUpload: '' };
case 'UPLOAD_SUCCESS':
return {
...state,
loadingUpload: false,
errorUpload: '',
};
case 'UPLOAD_FAIL':
return { ...state, loadingUpload: false, errorUpload: action.payload };
default:
return state;
}
}
export default function AdminProductEditScreen() {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const [isFeatured, setIsFeatured] = useState(false);
const [inStock, setInStock] = useState(false);
const [imports, setImports] = useState(false);
const [exports, setExports] = useState(false);
const [image, setImage] = useState([]);
const [category, setCategory] = useState([]);
const { query } = useRouter();
const productId = query.id;
const [
{ loading, error, loadingUpdate, loadingUpload }
dispatch,
] = useReducer(reducer, {
loading: true,
error: '',
});
const {
register,
handleSubmit,
control,
formState: { errors },
setValue,
} = useForm();
useEffect(() => {
const fetchData = async () => {
try {
dispatch({ type: 'FETCH_REQUEST' });
const { data } = await axios.get(`/api/admin/products/${productId}`);
dispatch({ type: 'FETCH_SUCCESS' });
setValue('name', data.name);
setValue('slug', data.slug);
setValue('headerTitle', data.headerTitle);
setImports(data.imports);
setExports(data.exports);
setValue('image', data.image);
} catch (err) {
dispatch({ type: 'FETCH_FAIL', payload: getError(err) });
}
};
fetchData();
}, [productId, setValue]);
const router = useRouter();
const uploadHandler = async (e, imageField = 'image') => {
const url = `https://api.cloudinary.com/v1_1/${process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}/upload`;
try {
dispatch({ type: 'UPLOAD_REQUEST' });
const {
data: { signature, timestamp },
} = await axios('/api/admin/cloudinary-sign');
const files = Array.from(e.target.files);
const formData = new FormData();
files.forEach((file) => {
formData.append('file', file);
});
formData.append('signature', signature);
formData.append('timestamp', timestamp);
formData.append('api_key', process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY);
const { data } = await axios.post(url, formData);
dispatch({ type: 'UPLOAD_SUCCESS' });
setValue(imageField, data.secure_url);
toast.success('File uploaded successfully');
} catch (err) {
dispatch({ type: 'UPLOAD_FAIL', payload: getError(err) });
toast.error(getError(err));
}
};
const updateCategory = (e) => {
const cats = e.target.files.split(',');
cats?.map((cat) => {
console.log(cat);
setCategory(cat);
});
};
const submitHandler = async ({
name,
slug,
headerTitle,
category,
price,
image,
}) => {
try {
dispatch({ type: 'UPDATE_REQUEST' });
await axios.put(`/api/admin/products/${productId}`, {
name,
slug,
headerTitle,
imports,
exports,
category,
image,
});
dispatch({ type: 'UPDATE_SUCCESS' });
toast.success('Product updated successfully');
router.push('/products');
} catch (err) {
dispatch({ type: 'UPDATE_FAIL', payload: getError(err) });
toast.error(getError(err));
}
};
return (
<Box title={`Edit Product ${productId}`} m="20px">
<Box display="flex" justifyContent="space-between">
<Header title="Product Edit" subtitle="List of Product to be edited" />
<Topbar />
</Box>
{loading && <CircularProgress></CircularProgress> ? (
<Box>Loading...</Box>
) : error ? (
<Box color="error">{error}</Box>
) : (
<ListItem width="100%">
<form onSubmit={handleSubmit(submitHandler)}>
<List sx={{ width: '60vw', border: '2px solid red' }}>
<ListItem>
<Controller
name="name"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="name"
label="Name"
error={Boolean(errors.name)}
helperText={errors.name ? 'Name is required' : ''}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="slug"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="slug"
label="Slug"
error={Boolean(errors.slug)}
helperText={errors.slug ? 'Slug is required' : ''}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="headerTitle"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="headerTitle"
label="header Title"
error={Boolean(errors.headerTitle)}
helperText={
errors.headerTitle ? 'headertitle is required' : ''
}
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<FormControlLabel
label="Import"
control={
<Checkbox
onClick={(e) => setImports(e.target.checked)}
checked={imports}
name="imports"
/>
}
></FormControlLabel>
</ListItem>
<ListItem>
<FormControlLabel
label="Export"
control={
<Checkbox
onClick={(e) => setExports(e.target.checked)}
checked={exports}
name="exports"
/>
}
></FormControlLabel>
</ListItem>
<ListItem>
<Controller
name="price"
control={control}
defaultValue=""
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="price"
label="Price"
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Controller
name="image"
control={control}
defaultValue=""
rules={{
required: true,
}}
render={({ field }) => (
<TextField
variant="outlined"
fullWidth
id="image"
label="Image"
{...field}
></TextField>
)}
></Controller>
</ListItem>
<ListItem>
<Button variant="contained" component="label">
Upload File
<input
type="file"
onChange={(e) => uploadHandler(e)}
hidden
multiple
/>
</Button>
{loadingUpload && <CircularProgress className="mx-4" />}
</ListItem>
{/** BUTTON */}
<ListItem>
<Button
variant="contained"
type="submit"
fullWidth
color="primary"
>
Update
</Button>
{loadingUpdate && <CircularProgress />}
</ListItem>
</List>
</form>
</ListItem>
)}
</Box>
);
}
AdminProductEditScreen.auth = { adminOnly: true };
i have tried in the uploadHandler function to loop through the e.target.files but only keeps sending a single image to the cloudinary api and its uploaded there and i can see it, even though i have selected multiple image files
CodePudding user response:
It looks like you're trying to send a single request to Cloudinary's API, where that single request has multiple values for file
.
That's not supported by the Cloudinary API - each call to the API should specify a single file
: https://cloudinary.com/documentation/image_upload_api_reference#upload_required_parameters
You should change your code so that when you loop through the array of files, you make a separate call to the API for each one, then continue after all files are uploaded.