Want to run server function every time a mutation happens on client side to get presignedUrl ...
Answered
Siricid woodwasp posted this in #help-forum
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
40 Replies
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 });
}
}
}
};
your await need to be called inside a useEffect if you really want to fetch data clientside.
You can do this like that:
You can do this like that:
useEffect(() => {
const fetchData = async () => {
const data = await getData();
...
};
fetchData();
}, []);
Siricid woodwaspOP
yea i did that it it doesnt work
why not?
Siricid woodwaspOP
ok let me try again it was throwing some weird error
ok 🤔
Siricid woodwaspOP
yea it throws this error SyntaxError: await is only valid in async functions and the top level bodies of modules
show your code
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 });
}
}
};
yea it needs to be in it's own function like this:
const fetchData = async () => {
const data = await getData();
...
};
fetchData();
you can see the full example here: https://nextjs-forum.com/post/1193597793558859907#message-1193598369197740172
Siricid woodwaspOP
k let me try
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
Siricid woodwaspOP
If I call this server action in parent server component it will only run once right?
look like the error coming from sst?
do you see error if you comment out the body of
uploadPresignedUrl
?Siricid woodwaspOP
yes no error then
maybe try changing the
target
in tsconfig.json
to esnext
if this doesn't work, try import the action to the page where the client component render and pass the action to it
Answer
Siricid woodwaspOP
then it works but url expires in 60 seconds to it just makes some mutattion rest fails
what is the error
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>
);
}
try this in
next.config.js
experimental: {
esmExternals: true
},
you didn't try to pass the action yet?
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
Siricid woodwaspOP
@Ray this works in dev mode will it keep woking when i bundle it
it should
Siricid woodwaspOP
any idea what is the type of action
for typescritp
just do
typeof uploadPresignedUrl
Siricid woodwaspOP
thanks btw I didnt thought this was possible i created seperate api for this lol was going to switch to that
I have dueled with it since 14.0.0 lol
and still haven't fixed yet in 14.0.4 lol