I'm currently trying to use the spread operator to push items into an array in React, but I'm getting the following error.
The error occurs in the PhotoUploader component within PlacesForm.jsx. I've included the code into the component below.
PlacesForm.jsx:
import axios from 'axios' import React, { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import AccountNav from './AccountNav' import Perks from './Perks' import PhotosUploader from './PhotosUploader' function PlacesForm() { const {id} = useParams() const [title, setTitle] = useState("") const [address, setAddress] = useState("") const [addedPhotos, setAddedPhotos] = useState([]) //state in question const [description, setDescription] = useState("") const [perks, setPerks] = useState([]) const [extraInfo, setExtraInfo] = useState("") const [checkIn, setCheckIn] = useState("") const [checkOut, setCheckOut] = useState("") const [maxGuests, setMaxGuests] = useState(1) useEffect(() => { if (!id) return const getPlace = async () => { try { const {data} = await axios.get(`/places/${id}`) setTitle(data.title) setAddress(data.address) setAddedPhotos(data.addedPhotos) setDescription(data.description) setPerks(data.perks) setExtraInfo(data.extraInfo) setCheckIn(data.checkIn) setCheckOut(data.checkOut) setMaxGuests(data.maxGuests) } catch (e) { console.log(e) } } getPlace() }, [id]) const navigate = useNavigate() const inputHeader = (text) => { return ( <h2 className='text-2xl mt-4'>{text}</h2> ) } const inputDescription = (text) => { return ( <p className="text-gray-500 text-sm">{text}</p> ) } const preInput = (header, description) => { return ( <> {inputHeader(header)} {inputDescription(description)} </> ) } const handleSubmit = async (e) => { e.preventDefault() const placeData = { title, address, addedPhotos, description, perks, extraInfo, checkIn, checkOut, maxGuests } if (id) { //update try { const res = await axios.put('/places', { id, ...placeData, }) navigate('/account/places') } catch (error) { console.log(error) } } else { //create new place try { const res = await axios.post('/places', placeData) navigate('/account/places') } catch (error) { console.log(error) } } } return ( <> <div> <AccountNav /> <form onSubmit={handleSubmit}> {preInput('Title', 'Something catchy and memorable')} <input type="text" placeholder="title" value={title} onChange={e => setTitle(e.target.value)} /> {preInput('Address', 'be specific')} <input type="text" placeholder="address" value={address} onChange={e => setAddress(e.target.value)} /> {preInput('Photos', 'Best to have at least 4')} <PhotosUploader addedPhotos={addedPhotos} setAddedPhotos={setAddedPhotos} />
This error occurs in the addPhotobyLink() and uploadPhoto() functions. The post request and backend are working fine and the "filename" is defined for both, but something is wrong with the "setAddedPhotos" part. Here is the component itself (PhotoUploader.jsx):
import axios from 'axios' import React, { useState } from 'react' function PhotosUploader({addedPhotos, setAddedPhotos}) { const [photoLink, setPhotoLink] = useState("") const addPhotoByLink = async (e) => { e.preventDefault() try { const {data:filename} = await axios.post('/upload-by-link', {link: photoLink}) setAddedPhotos(prev => [...prev, filename]) } catch (error) { console.log(error) } setPhotoLink("") } const uploadPhoto = async (e) => { const files = e.target.files const data = new FormData() for (let i = 0; i < files.length; i++) { data.append('photos', files[i]) } try { const {data:filenames} = await axios.post('/upload', data, { headers: {'Content-type': 'multipart/form-data'} }) setAddedPhotos(prev => [...prev, ...filenames]) } catch (error) { console.log(error) } } return ( <> <div className='flex gap-2' > <input value={photoLink} onChange={e => setPhotoLink(e.target.value)} type="text" placeholder="Add using link ....jpg" /> <button onClick={addPhotoByLink} className='bg-gray-200 px-4 rounded-2xl' > Add photo </button> </div> <div className="grid gap-2 grid-cols-3 lg:grid-cols-6 md:grid-cols-4 mt-2"> {addedPhotos?.length > 0 && addedPhotos.map((link) => ( <div key={link} className="h-32 flex"> <img src={'http://localhost:3000/uploads/' + link} alt="" className='rounded-2xl w-full object-cover position' /> </div> ))} <label className="h-32 cursor-pointer flex items-center justify-center gap-2 border bg-transparent rounded-2xl p-2 text-2xl text-gray-600"> <input type="file" multiple className='hidden' onChange={uploadPhoto}/> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-8 h-8"> <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" /> </svg> Upload </label> </div> </> ) } export default PhotosUploader
What I have tried so far:
setAddedPhotos(prev => [...prev, filename]) //didn't work setAddedPhotos([...prev, filename]) //didn't work setAddedPhotos([...addedPhotos, filename]) //didn't work, addPhotos isn't iterable error setAddedPhotos(prev => [...addedPhotos, filename]) //didn't work
P粉2935505752024-03-28 15:52:21
In React, it is generally not recommended to pass status update functions
One reason for doing this is that it can cause unnecessary re-rendering and make the code harder to reason about. If a child component receives the setAddedPhotos function as a prop and calls it, it will trigger a state update in the parent component, which in turn triggers a rerender of the parent component and all of its children. This can be a problem if the component tree is large and/or the state changes frequently, as it can cause a lot of unnecessary re-renders.
Another reason is that it may violate the principle of encapsulation, which is one of the main benefits of using React. Encapsulation means that each component should have its own independent state and behavior, and should not directly modify the state of its parent or sibling components. When a child component receives a state updater function as a prop, it can access and modify its parent component's state, which can make it more difficult to reason about the behavior of the component tree.
Instead of passing the setAddedPhotos function to the child component. You can pass the behavior function like this.
import React from 'react'; function PlacesForm() { const [addedPhotos, setAddedPhotos] = useState([]); const handleAddedPhotos = (newStates) => { setAddedPhotos(prev => [...prev, ...newStates]); }; return (); } function PhotosUploader({ addedPhotos, handleAddedPhotos }) { const uploadPhoto = async (e) => { const files = e.target.files const data = new FormData() for (let i = 0; i < files.length; i++) { data.append('photos', files[i]) } try { const {data:filenames} = await axios.post('/upload', data, { headers: {'Content-type': 'multipart/form-data'} }) handleAddedPhotos(filenames) } catch (error) { console.log(error) } } return ( <>> ); }