Cloudinary CldUploadWidget and Shadcn Popover event propagation issue makes the popover and the widget close

25 views Asked by At

So I'm aking this trello-like app, and I have my form for creating Boards in a shadcn Popover component. they can select a background image from unsplash or select their own, for that i made a FormPicker component, I wanted to use cloudinary for hosting the images but when i click the Upload your own button the event propagation makes everything close. I can see the widget for a second but then it's gone. This is my FormPicker component:

const FormPicker = ({ id, errors }: FormPickerProps) => {
  const { pending } = useFormStatus();
  const [images, setImages] = useState<Array<Record<string, any>>>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [selectedImageId, setSelectedImageId] = useState<string | null>(null);

  useEffect(() => {
    const fetchImages = async () => {
      try {
        const result = await unsplash.photos.getRandom({
          collectionIds: ["317099"],
          count: 9,
        });

        if (result && result.response) {
          const fetchedImages = result.response as Array<Record<string, any>>;
          setImages(fetchedImages);
        } else {
          console.log("Failed to fetch Images");
        }
      } catch (error) {
        console.log(error);
        setImages([defaultImages]);
      } finally {
        setIsLoading(false);
      }
    };

    fetchImages();
  }, []);
  
  const handleUploadSuccess = (result: any) => {
    console.log(result.info.secure_url);
    // Do something with the uploaded image URL
  };

  if (isLoading) {
    return (
      <div className="p-6 flex items-center justify-center">
        <Loader2 className="w-6 h-6 text-sky-700 animate-spin" />
      </div>
    );
  }

  return (
    <div className="relative">
      <p className="text-neutral-700 font-semibold text-xs mb-2">
        Select a background for your board
      </p>
      <div className="grid grid-cols-3 gap-2 mb-2">
        {images.map((img) => (
          <div
            key={img.id}
            className={cn(
              "aspect-video relative transition group hover:opacity-75 bg-muted shadow-sm cursor-pointer",
              pending && "opacity-50 hover:opacity-100 cursor-auto"
            )}
            onClick={() => {
              if (pending) return;
              setSelectedImageId(id);
            }}
          >
            <Image
              src={img.urls.thumb}
              alt={img.alt_description}
              fill
              className="object-cover rounded-sm"
            />
          </div>
        ))}
      </div>
      <div className="">
        <div className="relative z-10">
          <CldUploadWidget uploadPreset={process.env.CLOUDINARY_PRESET} onSuccess={handleUploadSuccess}>
            {({ open }) => {
              return (
                <Button
                  size="sm"
                  type="button"
                  disabled={pending}
                  className="text-xs text-right font-semibold text-neutral-600"
                  variant="ghost"
                  onClick={() => {open()}}
                >
                  Upload your own
                </Button>
              );
            }}
          </CldUploadWidget>
        </div>
      </div>
    </div>
  );
};

this is my FormPopover:

const FormPopover = ({
  children,
  side = "bottom",
  align,
  sideOffset = 0,
}: IFormPopoverProps) => {


  const { execute, fieldErrors } = useAction(createBoard, {
    onSuccess: (data) => {
      console.log({ data });
      toast.success("Board Created");
    },
    onError: (error) => {
      console.error(error);
      toast.error(error);
      // TODO: Cambiar color del icono de toast
    },
  });

  const onSubmit = (formData: FormData) => {
    const title = formData.get("title") as string;
    execute({ title });
  };




  return (
    <div>
      <Popover>
        <PopoverTrigger asChild>{children}</PopoverTrigger>
        <PopoverContent
          align={align}
          className="w-80 pt-3 "
          side={side}
          sideOffset={sideOffset}
        >
          <div className="text-sm font-medium text-center text-neutral-600 pb-4 ">
            Create Board
          </div>
          <PopoverClose asChild>
            <Button
              className="h-auto w-auto absolute text-neutral-600 right-2 top-2"
              variant="ghost"
            >
              <X className="h-4 w-4" />
            </Button>
          </PopoverClose>
          <form action={onSubmit} className="space-y-4">
            <div className="space-y-4">
              <FormPicker id="image" errors={fieldErrors} />
              <FormInput
                id="title"
                label="Board Title"
                type="text"
                errors={fieldErrors}
              />
             
            </div>
            <FormSubmit className="w-full" variant="primary">
              Create
            </FormSubmit>
          </form>
        </PopoverContent>
      </Popover>
    </div>
  );
};

export default FormPopover;

I tried using state to stop the event propagation but that stops the widget from showing up. I keep trying alternatives like that but still no luck

const FormPicker = ({ id, errors }: FormPickerProps) => {
  const { pending } = useFormStatus();
  const [images, setImages] = useState<Array<Record<string, any>>>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [selectedImageId, setSelectedImageId] = useState<string | null>(null);
  const [isWidgetOpen, setIsWidgetOpen] = useState(false);

  useEffect(() => {
    const fetchImages = async () => {
      try {
        const result = await unsplash.photos.getRandom({
          collectionIds: ["317099"],
          count: 9,
        });

        if (result && result.response) {
          const fetchedImages = result.response as Array<Record<string, any>>;
          setImages(fetchedImages);
        } else {
          console.log("Failed to fetch Images");
        }
      } catch (error) {
        console.log(error);
        setImages([defaultImages]);
      } finally {
        setIsLoading(false);
      }
    };

    fetchImages();
  }, []);

  const handleUploadSuccess = (result: any) => {
    console.log(result.info.secure_url);
    // Do something with the uploaded image URL
    setIsWidgetOpen(false);
  };

  const openUploadWidget = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.stopPropagation();
    setIsWidgetOpen(true);
  };

  const handleWidgetClose = () => {
    setIsWidgetOpen(false);
  };

  if (isLoading) {
    return (
      <div className="p-6 flex items-center justify-center">
        <Loader2 className="w-6 h-6 text-sky-700 animate-spin" />
      </div>
    );
  }

  return (
    <div className="relative">
      <p className="text-neutral-700 font-semibold text-xs mb-2">
        Select a background for your board
      </p>
      <div className="grid grid-cols-3 gap-2 mb-2">
        {images.map((img) => (
          <div
            key={img.id}
            className={cn(
              "aspect-video relative transition group hover:opacity-75 bg-muted shadow-sm cursor-pointer",
              pending && "opacity-50 hover:opacity-100 cursor-auto"
            )}
            onClick={() => {
              if (pending) return;
              setSelectedImageId(id);
            }}
          >
            <Image
              src={img.urls.thumb}
              alt={img.alt_description}
              fill
              className="object-cover rounded-sm"
            />
          </div>
        ))}
      </div>
      <div className="relative z-10">
        {isWidgetOpen && (
          <CldUploadWidget
            uploadPreset={process.env.CLOUDINARY_PRESET}
            onSuccess={handleUploadSuccess}
            onClose={handleWidgetClose}
            options={{ sources: ['local', 'url'] }}
          />
        )}
        <Button
          size="sm"
          type="button"
          disabled={pending}
          className="text-xs text-right font-semibold text-neutral-600"
          variant="ghost"
          onClick={openUploadWidget}
        >
          Upload your own
        </Button>
      </div>
    </div>
  );
};

Note: I already checked and the widget does render properly if it's not inside the popover. My app never breaks and the console doesn't log any error

0

There are 0 answers