W
Wasp3mo ago
wardbox

AWS file upload query cache invalidation after creation

Using version 0.14.2 on MacOS I have a query to get a team
query getTeamById {
fn: import { getTeamById } from "@src/server/queries.js",
entities: [Team, Organization, OrganizationRole, File]
}
query getTeamById {
fn: import { getTeamById } from "@src/server/queries.js",
entities: [Team, Organization, OrganizationRole, File]
}
And the team returns a logo (File) if there is one
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
const team = await context.entities.Team.findFirstOrThrow({
where: {
id: id
},
include: {
logo: true,
}
});
I also have a form that uploads a file and uses an action to do it.
action createFile {
fn: import { createFile } from "@src/server/actions.js",
entities: [User, Player, Team, File]
}
action createFile {
fn: import { createFile } from "@src/server/actions.js",
entities: [User, Player, Team, File]
}
return await context.entities.File.create({
data: {
name,
key,
uploadUrl,
type: fileType,
user: { connect: { id: context.user.id } },
team: {
connect: { id: teamId }
},
},
});
return await context.entities.File.create({
data: {
name,
key,
uploadUrl,
type: fileType,
user: { connect: { id: context.user.id } },
team: {
connect: { id: teamId }
},
},
});
When I upload the file and connect it to the team, I expect the getTeamById query to invalidate the cache since I have the File entity in the query and the Team entity in the createFile action. Right now when I upload a new photo, the query invalidates and errors out with a 404 from AWS because it can't find the file. If I do a full page refresh, it loads fine. Is there a way I could do a sleep or something to wait for it to finish uploading?
5 Replies
kapa.ai
kapa.ai3mo ago
Hi @wardbox. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the information provided, it seems like you're experiencing a timing issue where the cache invalidation and refetching of the getTeamById query is happening before the file has finished uploading to AWS. This results in a 404 error because the file isn't available at the time of the query. Wasp currently uses react-query for query management and automatic Entity-based Query cache invalidation. This means that Wasp invalidates a Query's cache whenever an Action that uses the same Entity is executed. In your case, the createFile action and getTeamById query both use the File and Team entities, so executing createFile causes the cached result of getTeamById to become outdated and Wasp invalidates it. This triggers a refetch of the data from the server. However, there's no built-in mechanism in Wasp to delay this refetch until the file has finished uploading. One possible workaround could be to use the mechanisms provided by react-query for manual cache invalidation. This would allow you to control when the cache is invalidated and the query is refetched. Another option could be to use optimistic updates. This allows you to optimistically set cache values after performing an Action. You can configure them using Wasp's useAction hook. This is currently the only manual cache invalidation mechanism Wasp supports natively. Please note that Wasp is planning to implement better support for direct invalidation/updates and turning off automatic invalidation when needed, as mentioned in this Github issue (https://github.com/wasp-lang/wasp/issues/63). For more information, you can refer to the following resources: - Wasp Docs: Cache Invalidation (https://wasp-lang.dev/docs/data-model/operations/actions#cache-invalidation) - Wasp Docs: The useAction Hook and Optimistic Updates (https://wasp-lang.dev/docs/data-model/operations/actions#the-useaction-hook-and-optimistic-updates) - Github Issue: Query invalidation/updating (https://github.com/wasp-lang/wasp/issues/63) ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
wardbox
wardboxOP3mo ago
@kapa.ai this is my onSubmit function, could i refactor it to move the axios.put at the action level instead of on the client?
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Yes, you can refactor your onSubmit function to move the axios.put request to the action level. This would involve creating a new action in your Wasp code that handles the file upload process. Here's a general idea of how you could do it: 1. Define a new action in your main.wasp file:
action uploadFile {
fn: import { uploadFile } from "@src/server/actions.js",
entities: [File]
}
action uploadFile {
fn: import { uploadFile } from "@src/server/actions.js",
entities: [File]
}
2. In your actions.js file, define the uploadFile function. This function should take the uploadUrl, file, and fileType as arguments, and use axios.put to upload the file:
import axios from 'axios';

export const uploadFile = async ({ uploadUrl, file, fileType }, context) => {
const res = await axios.put(uploadUrl, file, {
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}
return res.data;
}
import axios from 'axios';

export const uploadFile = async ({ uploadUrl, file, fileType }, context) => {
const res = await axios.put(uploadUrl, file, {
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}
return res.data;
}
3. In your client-side code, import the uploadFile action from wasp/client/operations and use it in your onSubmit function:
import { uploadFile } from 'wasp/client/operations';

// ...

const handleSubmit = async (event) => {
event.preventDefault();
// ...
const { uploadUrl } = await createFile({ fileType, name }, {});
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
try {
await uploadFile({ uploadUrl, file, fileType });
} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}
}
import { uploadFile } from 'wasp/client/operations';

// ...

const handleSubmit = async (event) => {
event.preventDefault();
// ...
const { uploadUrl } = await createFile({ fileType, name }, {});
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
try {
await uploadFile({ uploadUrl, file, fileType });
} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}
}
This way, the file upload process is handled on the server side, and your client-side code only needs to call the uploadFile action with the necessary arguments. Please note that this is a general guide and you might need to adjust the code to fit your specific use case. Also, remember to handle errors and edge cases appropriately in your code. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
wardbox
wardboxOP3mo ago
@kapa.ai I got it working with a different method. I ended up keeping the upload on the client side, setting a refetchLogo boolean at the team page level, passing it to both the form and the logo component, and manually refetching when the file is uploaded to S3.
const { data: logo, isLoading, error, refetch } = useQuery(getDownloadFileSignedURL, { key: logoKey },
{
enabled: refetchLogo,
}
);

useEffect(() => {
if (refetchLogo) {
refetch();
}
setRefetchLogo(false);
}, [refetchLogo]);
const { data: logo, isLoading, error, refetch } = useQuery(getDownloadFileSignedURL, { key: logoKey },
{
enabled: refetchLogo,
}
);

useEffect(() => {
if (refetchLogo) {
refetch();
}
setRefetchLogo(false);
}, [refetchLogo]);
if (res.status === 200) {
toast({
description: ":smile_cat: Team logo uploaded",
})
setRefetchLogo(true);
if (res.status === 200) {
toast({
description: ":smile_cat: Team logo uploaded",
})
setRefetchLogo(true);
kapa.ai
kapa.ai3mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- That's a great solution! You've effectively created a mechanism to manually trigger a refetch of the logo data when a new file is uploaded. By using a refetchLogo boolean and a useEffect hook, you're able to control when the getDownloadFileSignedURL query is refetched. Here's a brief explanation of your solution for those who might find it helpful: 1. You're using the useQuery hook from react-query to fetch the logo data. The enabled option is set to the value of refetchLogo. This means the query will only run when refetchLogo is true.
const { data: logo, isLoading, error, refetch } = useQuery(getDownloadFileSignedURL, { key: logoKey },
{
enabled: refetchLogo,
}
);
const { data: logo, isLoading, error, refetch } = useQuery(getDownloadFileSignedURL, { key: logoKey },
{
enabled: refetchLogo,
}
);
2. You're using a useEffect hook to trigger a refetch of the logo data whenever refetchLogo changes. If refetchLogo is true, the refetch function returned by useQuery is called, and refetchLogo is set back to false.
useEffect(() => {
if (refetchLogo) {
refetch();
}
setRefetchLogo(false);
}, [refetchLogo]);
useEffect(() => {
if (refetchLogo) {
refetch();
}
setRefetchLogo(false);
}, [refetchLogo]);
3. When a new file is successfully uploaded, you're setting refetchLogo to true. This triggers the useEffect hook, which in turn triggers a refetch of the logo data.
if (res.status === 200) {
toast({
description: ":smile_cat: Team logo uploaded",
})
setRefetchLogo(true);
}
if (res.status === 200) {
toast({
description: ":smile_cat: Team logo uploaded",
})
setRefetchLogo(true);
}
This is a clever use of react-query's features to manually control when data is refetched. Great job! ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @wardbox, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Want results from more Discord servers?
Add your server