Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions frontends/api/src/hooks/articles/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useRef } from "react"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import type { AxiosProgressEvent } from "axios"

Expand Down Expand Up @@ -51,19 +52,46 @@ const useArticleCreate = () => {
}

export const useMediaUpload = () => {
return useMutation({
mutationFn: async (data: {
file: File
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void
}) => {
const nextProgressCb = useRef<((percent: number) => void) | undefined>(
undefined,
)

const mutation = useMutation({
mutationFn: async (data: { file: File }) => {
const response = await mediaApi.mediaUpload(
{ image_file: data.file },
{ onUploadProgress: data.onUploadProgress },
{
onUploadProgress: (e: AxiosProgressEvent) => {
const percent = Math.round((e.loaded * 100) / (e.total ?? 1))
nextProgressCb.current?.(percent)
},
},
)

return response.data
},
onSettled: () => {
nextProgressCb.current = undefined
},
})

return {
...mutation,
/**
* Set a callback to be called on the next upload progress event.
*
* NOTES:
* - This callback will be cleared after the mutation settles (either success or error).
* - This is a separate method, not part of the mutate/mutateAsync options,
* to avoid errors with function serialization. (E.g., Tanstack Query
* devtools attempt to serialize mutation options.)
*/
setNextProgressCallback: (
callback: ((percent: number) => void) | undefined,
) => {
nextProgressCb.current = callback
},
}
}

const useArticleDestroy = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,10 @@ const ArticleEditor = ({ onSave, readOnly, article }: ArticleEditorProps) => {
file,
async (file: File, progressCb?: (percent: number) => void) => {
try {
const response = await uploadImage.mutateAsync({
file,
onUploadProgress: (e) => {
const percent = Math.round((e.loaded * 100) / (e.total ?? 1))
progressCb?.(percent)
},
})
uploadImage.setNextProgressCallback(progressCb)
Copy link
Contributor Author

@ChristopherChudzicki ChristopherChudzicki Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the reason the error happens is:

  1. Tanstack query devtools are a chrome extension, so they run in a webworker
  2. The devtools track mutation state, which includes the args we pass to mutate (or mutateAsync).
  3. In order for that data to get into the webworker, the data must be serializable. Previously we were passing a function, which was not serializable.

Now onUploadProgress is no longer an arg of mutateasync, which avoids the issue.

I don't love changing the application code to avoid a devtool error. However, it is useful to not have these errors showing up, and this alternative implementation seems fine.


const response = await uploadImage.mutateAsync({ file })

if (!response?.url) throw new Error("Upload failed")
return response.url
} catch (error) {
Expand Down
Loading