简体   繁体   中英

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM