📑

【NextJs14】NextJs14 と 便利なライブラリ【#26 Upload Images Preview】

2024/01/31に公開

【#26 Upload Images Preview】

YouTube: https://youtu.be/hCbDmfsV8HU

https://youtu.be/hCbDmfsV8HU

今回は選択した画像のプレビューの表示と
選択を解除するボタンを実装します。

app/(main)/images/_components/images-form.tsx
"use client";

import { useState } from "react";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

import { UploadImagesForm } from "@/components/forms/upload-images-form";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
} from "@/components/ui/form";

const formSchema = z.object({
  images: z.object({ url: z.string() }).array(),
});

export const ImagesForm = () => {
  const [isLoading, setIsLoading] = useState(false);
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      images: [],
    },
  });

  const onSubmit = (values: z.infer<typeof formSchema>) => {
    setIsLoading(true);
    alert(JSON.stringify(values.images, null, 2));
    setIsLoading(false);
  };

  return (
    <Form {...form}>
      <form
        onSubmit={form.handleSubmit(onSubmit)}
        className="flex flex-col space-y-4"
      >
        <FormField
          control={form.control}
          name="images"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Images upload button</FormLabel>
              <FormControl>
                <UploadImagesForm
                  value={field.value.map((image) => image.url)}
                  onChange={(url: string) =>
                    field.onChange([...field.value, { url }])
                  }
                  onRemove={(url: string) =>
                    field.onChange([
                      ...field.value.filter((current) => current.url !== url),
                    ])
                  }
                  disabled={isLoading}
                />
              </FormControl>
            </FormItem>
          )}
        />
        <Button type="submit" variant="primary" disabled={isLoading}>
          Submit
        </Button>
      </form>
    </Form>
  );
};
conponents/forms/upload-images-form.tsx
"use client";

import Image from "next/image";
import { CldUploadWidget } from "next-cloudinary";
import { ImagePlus, Trash } from "lucide-react";
import { useIsClient } from "usehooks-ts";

import { Button } from "@/components/ui/button";

interface UploadImagesFormProps {
  onChange: (url: string) => void;
  onRemove: (url: string) => void;
  value: string[];
  disabled?: boolean;
}

export const UploadImagesForm = ({
  onChange,
  onRemove,
  value,
  disabled,
}: UploadImagesFormProps) => {
  const isClient = useIsClient();

  const onUpload = (result: any) => {
    onChange(result.info.secure_url);
  };

  if (!isClient) {
    return null;
  }

  return (
    <div>
      <div className="flex items-center gap-3 mb-3">
        {value.map((url) => (
          <div className="relative w-[16em] h-[9em] rounded-md overflow-hidden">
            <div className="z-50 absolute top-3 right-3">
              <Button
                type="button"
                disabled={disabled}
                onClick={() => onRemove(url)}
              >
                <Trash className="h-4 w-4" />
              </Button>
            </div>
            <Image src={url} alt="image" fill className="object-cover" />
          </div>
        ))}
      </div>
      <CldUploadWidget onUpload={onUpload} uploadPreset="iv6ye3lb">
        {({ open }) => {
          const onClick = () => {
            open();
          };
          return (
            <Button type="button" variant="default" onClick={onClick}>
              <ImagePlus className="w-4 h-4 mr-2" />
              Upload your images
            </Button>
          );
        }}
      </CldUploadWidget>
    </div>
  );
};

Discussion