Home > Mobile >  how to upload an array of images to cloudinary using axios and react hook form using next js
how to upload an array of images to cloudinary using axios and react hook form using next js

Time:12-16

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.

  • Related