Next.js Discord

Discord Forum

Want to run server function every time a mutation happens on client side to get presignedUrl ...

Answered
Siricid woodwasp posted this in #help-forum
Open in Discord
Avatar
Siricid woodwaspOP
export async function uploadPresignedUrl() {
  const user = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: (contextSpec) => {
      return fetchAuthSession(contextSpec);
    },
  });

  if (!user) {
    throw new Error("No user");
  }
  const bucket = Bucket.bucket.bucketName;
  const command = new PutObjectCommand({
    Bucket: bucket,
    ACL: "public-read",
    Key: crypto.randomUUID(),
  });
  const client = new S3Client();

  const url = await getSignedUrl(client, command, {
    expiresIn: 60,
  });
  return {
    url,
  };
}
Answered by Ray
if this doesn't work, try import the action to the page where the client component render and pass the action to it
View full answer

40 Replies

Avatar
Siricid woodwaspOP
calling it on client side
export clientcomponent = () => {

const handleUpload = async () => {
    const { url } = await uploadPresignedUrl();
    if (onChange) {
      if (Array.isArray(acceptedFiles)) {
        acceptedFiles.forEach((file) => {
          mutation.mutate({ file, url });
        });
      } else {
        mutation.mutate({ file: acceptedFiles, url });
      }
    }
  }
};
Avatar
your await need to be called inside a useEffect if you really want to fetch data clientside.

You can do this like that:
useEffect(() => {
        const fetchData = async () => {
            const data = await getData();
...
        };

        fetchData();
    }, []);
Avatar
Siricid woodwaspOP
yea i did that it it doesnt work
Avatar
why not?
Avatar
Siricid woodwaspOP
ok let me try again it was throwing some weird error
Avatar
ok 🤔
Avatar
Siricid woodwaspOP
yea it throws this error SyntaxError: await is only valid in async functions and the top level bodies of modules
Avatar
show your code
Avatar
Siricid woodwaspOP
export const UploadFileComponent = () => {
  const [onChange, setOnChange] = useState<File | File[] | null>(null);
  const [progress, setProgress] = useState<Record<string, number>>({});
  const [url, setUrls] = useState<string | null>();

  const onDrop = useCallback((acceptedFiles: File[]) => {
    setOnChange(acceptedFiles);
  }, []);
  const { getRootProps, getInputProps, isDragActive, acceptedFiles } =
    useDropzone({
      onDrop,
    });

  useEffect(() => {
    (async () => {
      const { url } = await uploadPresignedUrl();
      setUrls(url);
    })();
  }, []);

  const mutation = useMutation({
    mutationKey: ["fileUpload"],
    mutationFn: async ({ file }: { file: File }) => {
      if (!url) {
        throw new Error("No url provided");
      }
      await axios.put(url, file, {
        headers: {
          "Content-Type": file.type,
          "Content-Disposition": `attachment; filename="${file.name}"`,
        },
        onUploadProgress: (progressEvent) => {
          setProgress((prevProgress) => ({
            ...prevProgress,
            [file.name]: Math.round(
              (progressEvent.loaded * 100) / (progressEvent.total ?? 0)
            ),
          }));
        },
      });
      return file;
    },
    onSuccess: (file) => {
      toast(`File ${file?.name} uploaded`, {
        description: `File type: ${file?.type.split("/")[1]}, Size: ${
          file?.size
        }`,
      });
    },
    onError: (file) => {
      toast(`Couldn't upload file ${file?.name}`);
    },
  });

  const handleUpload = async () => {
    if (onChange) {
      if (Array.isArray(acceptedFiles)) {
        acceptedFiles.forEach((file) => {
          mutation.mutate({ file });
        });
      } else {
        mutation.mutate({ file: acceptedFiles });
      }
    }
  };
Avatar
yea it needs to be in it's own function like this:
const fetchData = async () => {
            const data = await getData();
...
        };

        fetchData();
Avatar
Siricid woodwaspOP
k let me try
Avatar
Siricid woodwaspOP
async function getURL() {
    const { url } = await uploadPresignedUrl();
    return url;
  }


  const handleUpload = async () => {
    const url = await getURL();
    if (onChange) {
      if (Array.isArray(acceptedFiles)) {
        acceptedFiles.forEach((file) => {
          mutation.mutate({ file, url });
        });
      } else {
        mutation.mutate({ file: acceptedFiles, url });
      }
    }
  };

Doesnt work either
Image
Avatar
Siricid woodwaspOP
If I call this server action in parent server component it will only run once right?
Avatar
look like the error coming from sst?
do you see error if you comment out the body of uploadPresignedUrl?
Avatar
Siricid woodwaspOP
yes no error then
Avatar
maybe try changing the target in tsconfig.json to esnext
Avatar
if this doesn't work, try import the action to the page where the client component render and pass the action to it
Answer
Avatar
Siricid woodwaspOP
Image
then it works but url expires in 60 seconds to it just makes some mutattion rest fails
Avatar
what is the error
Avatar
Siricid woodwaspOP
Error:+await+is+only+valid+in+async+functions+and+the+top+level+bodies+of+modules
import { UploadFileComponent } from "@/components/upload-file";
import { Metadata } from "next";

export const dynamic = "force-dynamic";

export const metadata: Metadata = {
  title: "Settings",
};

export default async function SettingsPage() {
  return (
    <div>
      <h1>Settings</h1>
      <UploadFileComponent />
    </div>
  );
}
Avatar
try this in next.config.js
experimental: {
    esmExternals: true
  },
you didn't try to pass the action yet?
Avatar
Siricid woodwaspOP
this is where next client component is called if i call action here it just runs once
i need to run it on every mutation
Avatar
you don't need to call
just pass it
<UploadFileComponent action={uploadPresignedUrl} />
Avatar
Siricid woodwaspOP
@Ray this works in dev mode will it keep woking when i bundle it
Avatar
it should
Avatar
Siricid woodwaspOP
any idea what is the type of action
for typescritp
Avatar
just do typeof uploadPresignedUrl
Avatar
Siricid woodwaspOP
thanks btw I didnt thought this was possible i created seperate api for this lol was going to switch to that
Avatar
I have dueled with it since 14.0.0 lol
and still haven't fixed yet in 14.0.4 lol