Shadcn React表单图片上传异常:上传第二张时数组被覆盖
问题:Shadcn React表单图片上传时数组被覆盖而非追加
基于Shadcn构建的React表单中,通过自定义ImageUpload组件(集成next-cloudinary上传组件)实现图片预览后上传至数据库的功能。单张图片上传一切正常,但上传第二张图片时,表单的images数组会被覆盖,而非追加新的图片对象。
相关代码
FormField代码
<FormField control={form.control} name="images" render={({ field }) => { return ( <FormItem> <FormLabel>Images</FormLabel> <FormControl> <ImageUpload value={field.value.map((image) => image.url)} disabled={loading} onChange={(url) => field.onChange([...field.value, { url }])} // 新增图片时调用 onRemove={(url) => field.onChange([...field.value.filter((current) => current.url !== url)])} // 删除图片时调用 /> </FormControl> <FormMessage /> </FormItem> ) }} />
ImageUpload组件代码(基于next-cloudinary)
'use client' import { useEffect, useState } from 'react' import { Button } from './button' import { ImagePlus, Trash } from 'lucide-react' import Image from 'next/image' import { CldUploadWidget, CloudinaryUploadWidgetResults } from 'next-cloudinary' interface ImageUploadProps { disabled?: boolean onChange: (value: string) => void onRemove: (value: string) => void value: string[] } function ImageUpload({ disabled, onChange, onRemove, value }: ImageUploadProps) { const [isMounted, setIsMounted] = useState(false) useEffect(() => { setIsMounted(true) }, []) console.log('value:', value) const onUpload = (result: CloudinaryUploadWidgetResults) => { const info = result.info as object console.log('start') console.log(value) if ('secure_url' in info) { const url = info.secure_url as string console.log('Uploaded URL:', url) onChange(url) } } if (!isMounted) return null return ( <div> <div className="mb-4 flex items-center gap-4"> {value.map((url) => ( <div key={url} className="relative h-[200px] w-[200px] overflow-hidden rounded-md"> <div className="absolute right-2 top-2 z-10"> <Button type="button" onClick={() => onRemove(url)} variant="destructive" size="icon"> <Trash className="h-4 w-4" /> </Button> </div> <Image fill className="object-cover" alt="image" src={url} /> </div> ))} </div> <CldUploadWidget onSuccess={onUpload} uploadPreset="erx1yvtk"> {({ open }) => { const onClick = () => { open() } return ( <Button type="button" disabled={disabled} variant="secondary" onClick={onClick}> <ImagePlus className="mr-2 h-4 w-4" /> Upload an Image </Button> ) }} </CldUploadWidget> </div> ) } export default ImageUpload
尝试过的代码及错误
尝试用函数式更新修改状态,虽能正常预览两张图片,但出现错误提示:期望数组而非函数
const handleImageChange = (url: string) => { // 更新表单状态,添加新图片 field.onChange((prev: Image[]) => [...(prev || []), { url }]) } // 处理图片删除 const handleImageRemove = (url: string) => { // 更新表单状态,过滤掉要删除的图片 field.onChange((prev: Image[]) => (prev || []).filter((image) => image.url !== url) as Image[]) }
问题原因
- 最初写法存在闭包陷阱:组件渲染时捕获的
field.value是初始值,每次上传新图片都会基于这个初始值创建数组,导致之前的图片被覆盖。 - 函数式更新写法报错,是因为React Hook Form的
field.onChange默认只接受具体值,不支持传入更新函数(setValue方法才支持函数式更新)。
解决方案
改用form.setValue实现函数式更新,确保每次基于最新表单状态修改数组:
<FormField control={form.control} name="images" render={({ field }) => { // 处理图片新增 const handleImageChange = (url: string) => { form.setValue('images', (prev = []) => [...prev, { url }]) } // 处理图片删除 const handleImageRemove = (url: string) => { form.setValue('images', (prev = []) => prev.filter(image => image.url !== url)) } return ( <FormItem> <FormLabel>Images</FormLabel> <FormControl> <ImageUpload value={field.value.map((image) => image.url)} disabled={loading} onChange={handleImageChange} onRemove={handleImageRemove} /> </FormControl> <FormMessage /> </FormItem> ) }} />
补充说明
form.setValue支持传入更新函数,参数为当前最新的字段值,可避免闭包导致的旧值问题。- 给
prev设置默认值[],确保字段初始值为undefined时也能正常工作。
内容的提问来源于stack exchange,提问作者Alessio Tortora
相关产品推荐
相关产品推荐

