Want to run server function every time a mutation happens on client side to get presignedUrl ...
Answered
Siricid woodwasp posted this in #help-forum
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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](https://cdn.discordapp.com/avatars/301376057326567425/788b75eb8862277af3d31d317ada783f.webp?size=256)
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();
}, []);
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
yea i did that it it doesnt work
![Avatar](https://cdn.discordapp.com/avatars/301376057326567425/788b75eb8862277af3d31d317ada783f.webp?size=256)
why not?
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
ok let me try again it was throwing some weird error
![Avatar](https://cdn.discordapp.com/avatars/301376057326567425/788b75eb8862277af3d31d317ada783f.webp?size=256)
ok 🤔
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
yea it throws this error SyntaxError: await is only valid in async functions and the top level bodies of modules
![Avatar](https://cdn.discordapp.com/avatars/301376057326567425/788b75eb8862277af3d31d317ada783f.webp?size=256)
show your code
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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](https://cdn.discordapp.com/avatars/301376057326567425/788b75eb8862277af3d31d317ada783f.webp?size=256)
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
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
k let me try
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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](https://cdn.discordapp.com/attachments/1193597793558859907/1193602350380744724/image.png?ex=65ad4fd0&is=659adad0&hm=6612e00e203c776cc2234868d96d270d42fe3d1a058eaff485f2f7b82f6d457b&)
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
If I call this server action in parent server component it will only run once right?
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
@Siricid woodwasp Click to see attachment
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
look like the error coming from sst?
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
@Siricid woodwasp Click to see attachment
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
do you see error if you comment out the body of
uploadPresignedUrl
?![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
yes no error then
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
@Siricid woodwasp yes no error then
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
maybe try changing the
target
in tsconfig.json
to esnext
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
@Ray maybe try changing the `target` in `tsconfig.json` to `esnext`
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
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](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
![Image](https://cdn.discordapp.com/attachments/1193597793558859907/1193606836662849556/image.png?ex=65ad53fe&is=659adefe&hm=c95007c556a1a9fe73fc7494ae204d856a9e156561d959e5582868a50e180bf4&)
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
@Ray if this doesn't work, try import the action to the page where the client component render and pass the action to it
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
then it works but url expires in 60 seconds to it just makes some mutattion rest fails
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
what is the error
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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](https://cdn.discordapp.com/embed/avatars/3.png)
@Siricid woodwasp Error:+await+is+only+valid+in+async+functions+and+the+top+level+bodies+of+modules
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
try this in
next.config.js
experimental: {
esmExternals: true
},
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
@Siricid woodwasp ts
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](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
you didn't try to pass the action yet?
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
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](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
@Ray this works in dev mode will it keep woking when i bundle it
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
any idea what is the type of action
for typescritp
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
just do
typeof uploadPresignedUrl
![Avatar](https://cdn.discordapp.com/embed/avatars/3.png)
Siricid woodwaspOP
thanks btw I didnt thought this was possible i created seperate api for this lol was going to switch to that
![Avatar](https://cdn.discordapp.com/avatars/743561772069421169/3c5d689eee4459a24fc420063411c016.webp?size=256)
I have dueled with it since 14.0.0 lol
and still haven't fixed yet in 14.0.4 lol