Bindo Thorpe
Bindo Thorpe

Reputation: 1

Vercel Blob storage with NextJS 14 server action unexpected behaviour

I am creating an application as a hobby to learn NextJS. I am currently trying to work with Vercel Blob storage and I have been able to upload images using the template listed in their documentation.

But when I try to create my own component to use it in, it has some unexpected behaviour.

I created a wrapper component that is a server component, and a client component with the form.

EditImageModalWrapper.tsx

import { put } from "@vercel/blob";
import { revalidatePath } from "next/cache";
import EditImageModal from "./EditImageModal";

export default async function EditImageModalWrapper(props: {
  isOpen: boolean;
  onClose: () => void;
}) {
  async function uploadImage(formData: FormData) {
    "use server";

    console.log("Server function called");

    const imageFile = formData.get("image") as File;

    const blob = await put(imageFile.name, imageFile, {
      access: "public",
    });

    console.log(blob);

    revalidatePath("/edit");
    revalidatePath("/");
  }

  return (
    <EditImageModal
      isOpen={props.isOpen}
      onClose={props.onClose}
      serverAction={uploadImage}
    />
  );
}

EditImageModal

import {
  Button,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
} from "@nextui-org/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUpFromBracket } from "@fortawesome/free-solid-svg-icons";
import { useRef } from "react";

export default function EditImageModal(props: {
  isOpen: boolean;
  onClose: () => void;
  serverAction: (formData: FormData) => void;
}) {
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (fileInputRef.current) {
      fileInputRef.current.click();
    }
  };

  return (
    <Modal isOpen={props.isOpen} onOpenChange={props.onClose} placement="auto">
      <form action={props.serverAction}>
        <ModalContent>
          <ModalHeader className="flex flex-col gap-1">Edit Image</ModalHeader>
          <ModalBody>
            <div
              className={`w-full h-64 flex justify-center items-center flex-col gap-4 font-bold hover:cursor-pointer border-2 border-dashed rounded-lg border-black`}
              onClick={handleClick}
            >
              <FontAwesomeIcon
                icon={faArrowUpFromBracket}
                size="2x"
                color="#222222"
              />
              <span>Upload an image</span>
              <input
                ref={fileInputRef}
                type="file"
                accept="image/*"
                id="image"
                name="image"
                style={{ display: "none" }}
              />
            </div>
          </ModalBody>
          <ModalFooter className="flex justify-between">
            <Button color="default" variant="bordered" onPress={props.onClose}>
              Cancel
            </Button>
            <Button color="primary" type="submit">
              Save
            </Button>
          </ModalFooter>
        </ModalContent>
      </form>
    </Modal>
  );
}

As docmented on Vercel itself, you can pass server actions as props. But when I click the submit button, my page refreshes and my url becomes http://localhost:3000/?image=IMAGE_NAME.png. I also don't get any logs in my server cli or my client cli.

Does someone know why I get this result?

I tried uploading an image and getting the url from the blob, but instead my page refreshes and my url changes.

Upvotes: 0

Views: 382

Answers (1)

Bindo Thorpe
Bindo Thorpe

Reputation: 1

I managed to make it work.

The first thing I did was move the server action to its separate file.


export async function uploadImage(formData: FormData): Promise<void> {

  const image = formData.get("image") as File;
  const blob = await put(image.name, image, {
    access: "public",
  });

  console.log(blob);

  revalidatePath("/edit");
  revalidatePath("/");
}

And then I imported this server action in my file with my form.

import {
  Button,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
} from "@nextui-org/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUpFromBracket } from "@fortawesome/free-solid-svg-icons";
import { useRef } from "react";
import { uploadImage } from "../../actions";

export default function EditImageModal(props: {
  isOpen: boolean;
  onClose: () => void;
  serverAction: (formData: FormData) => void;
}) {
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (fileInputRef.current) {
      fileInputRef.current.click();
    }
  };

  return (
    <Modal isOpen={props.isOpen} onOpenChange={props.onClose} placement="auto">
      <form action={uploadImage}>
        <ModalContent>
          <ModalHeader className="flex flex-col gap-1">Edit Image</ModalHeader>
          <ModalBody>
            <div
              className={`w-full h-64 flex justify-center items-center flex-col gap-4 font-bold hover:cursor-pointer border-2 border-dashed rounded-lg border-black`}
              onClick={handleClick}
            >
              <FontAwesomeIcon
                icon={faArrowUpFromBracket}
                size="2x"
                color="#222222"
              />
              <span>Upload an image</span>
              <input
                ref={fileInputRef}
                type="file"
                accept="image/*"
                id="image"
                name="image"
                style={{ display: "none" }}
              />
            </div>
          </ModalBody>
          <ModalFooter className="flex justify-between">
            <Button color="default" variant="bordered" onPress={props.onClose}>
              Cancel
            </Button>
            <Button color="primary" type="submit">
              Save
            </Button>
          </ModalFooter>
        </ModalContent>
      </form>
    </Modal>
  );
}

Upvotes: 0

Related Questions