on NEXTJS v15 Cloudinary secure_url returning undefined but the img do get uploaded in cloudinary
-
Console IMG: onUpload full function
-
Console IMG: onUpload full result
Code Issue:
const onUpload = (result: { info?: string | { secure_url?: string } }) => {
console.log('Upload result:', result); // Debug log
const secureUrl = typeof result.info === 'string' ? result.info : result.info?.secure_url;
if (secureUrl) {
onChange(secureUrl);
} else {
console.error('secure_url not found in upload result:', result);
}
};
Image-upload.tsx
'use client';
// global import
import React, { useEffect, useState } from 'react';
import { CldUploadWidget } from 'next-cloudinary'; // Replace 'some-library' with the actual library name
// local import
import { Button } from '@/components/ui/button';
import { ImagePlusIcon, Trash } from 'lucide-react';
import Image from 'next/image';
interface ImageUploadProps {
disabled?: boolean;
onChange: (value: string) => void;
onRemove: (value: string) => void;
value: string[];
}
const ImageUpload: React.FC<ImageUploadProps> = ({
disabled,
onChange,
onRemove,
value,
}) => {
// State to track if the component is mounted
const [isMounted, setIsMounted] = useState(false);
// useEffect hook to set the isMounted state to true after the component mounts
useEffect(() => {
setIsMounted(true);
}, []);
const onUpload = (result: { info?: string | { secure_url?: string } }) => {
console.log('Upload result:', result); // Debug log
const secureUrl = typeof result.info === 'string' ? result.info : result.info?.secure_url;
if (secureUrl) {
onChange(secureUrl);
} else {
console.error('secure_url not found in upload result:', result);
}
};
// If the component is not mounted, return null to prevent server-side rendering
if (!isMounted) return null;
return (
<React.Fragment>
{/* Display the uploaded images */}
<div className='mb-4 flex items-center gap-4'>
{value.map(url => (
<div
key={url}
className='relative w-[200px] h-[200px] rounded-md overflow-hidden'
>
<div className='z-10 absolute top-2 right-2'>
<Button
key={`remove-${url}`}
id='image-remove'
data-testid='image-remove'
type='button'
onClick={() => {
onRemove(url);
}}
variant='destructive'
size='sm'
>
<Trash className='h-4 w-4' />
</Button>
</div>
{url && (
<Image
layout='fill'
className='object-cover'
alt='Image'
src={url}
id='image'
data-testid='image'
/>
)}
</div>
))}
</div>
{/* this is to open the image upload widget */}
<CldUploadWidget onUploadAdded={onUpload} uploadPreset='ecommerce'>
{({ open }) => {
const onClick = () => {
open();
};
return (
<Button
type='button'
onClick={onClick}
disabled={disabled}
variant='secondary'
id='image-upload'
data-testid='image-upload'
>
<ImagePlusIcon className='h-4 w-4 mr-2' />
Upload Image
</Button>
);
}}
</CldUploadWidget>
</React.Fragment>
);
};
export default ImageUpload;
**Useage **
const formSchema = z.object({
label: z
.string()
.nonempty('Billboard label is required')
.min(3, 'Billboard label is too short'),
imageUrl: z.string().nonempty('Billboard image is required'),
});
type BillboardFormValues = z.infer<typeof formSchema>;
const form = useForm<BillboardFormValues>({
resolver: zodResolver(formSchema),
defaultValues: initialData || { label: '', imageUrl: '' },
});
<FormField
control={form.control}
name='imageUrl'
render={({ field }) => (
<FormItem>
<FormLabel data-testid='Billboard-imageUrl'>
Background image
</FormLabel>
<FormControl>
<ImageUpload
value={
Array.isArray(field.value)
? field.value.map((image: { url: string }) => image.url)
: []
}
disabled={loading}
onChange={url => {
const newValue = Array.isArray(field.value)
? [...field.value, { url }]
: [{ url }];
field.onChange(newValue);
}}
onRemove={url => {
const newValue = Array.isArray(field.value)
? field.value.filter(
(current: { url: string }) => current.url !== url,
)
: [];
field.onChange(newValue);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>