I'm working on a state machine to handle uploading a file. During uploading, I'd like to show the user the progress. Here's what I have so far:
// machine.tsx
import upload from './actors/upload'
const machine = createMachine({
id: 'Document Upload Machine',
context: ({ input: { file } }) => ({
created: Date.now(),
file,
name: getDocumentTitle(file.name)
}),
initial: 'init',
states: {
init: {},
uploading: {
entry: 'resetProgress',
invoke: {
src: 'upload',
input: ({ context: { file, upload } }) => ({ file, urls: upload.urls }),
onDone: {
target: 'startProcessing',
actions: assign({ upload: ({ context, event }) => ({ ...context.upload, parts: event.output }) }),
},
onError: {
target: 'failed',
actions: assign({ error: 'upload' }),
},
},
on: {
PROGRESS: {
actions: {
type: 'setProgress',
params: ({ event }) => ({ value: event.value })
}
}
}
},
...
}
}, {
actions: {
resetProgress: assign({ progress: 0 }),
setProgress: assign({ progress: (_, { value }) => Math.round(value) }),
},
actors: {
upload
}
})
// actors/upload.tsx
interface Input {
file: File
urls: string[]
}
const upload = fromPromise<IMultipartPart[], Input>(async ({ input: { file, urls }, self }) => {
const partCount = urls.length
const total = file.size
const overallProgress = Array.from({ length: partCount }).map(() => 0)
const _onUploadProgress = (part: number) => (event: ProgressEvent) => {
overallProgress[part - 1] = event.loaded
const loaded = overallProgress.reduce((loaded, part) => loaded + part, 0)
console.log('Sending progress to parent', (loaded * 100) / total)
sendParent({ type: 'PROGRESS', value: (loaded * 100) / total })
}
const parts = await Promise.all(
urls.map(async (url, index) => {
const partNumber = index + 1
return uploadPart(
url,
{ file, partCount, partNumber },
{ onUploadProgress: _onUploadProgress(partNumber) }
)
})
)
return parts
})
While I'm seeing Sending progress to parent in the console, a breakpoint in the PROGRESS event for the uploading state is not hit, showing that the event is not being picked up by the parent machine, so the progress never gets updated. I've scoured the v5 documentation, the jsdocs site for the API, the xstate GitHub issues and discussions and pretty much exhausted Google searching for an answer on how to properly send events from an Actor to Parent.
To my understanding, in xstate v5, a Promise Actor can send events to its parent via sendParent. What am I doing wrong or misunderstanding?
I posted this question in the GitHub Discussions for xstate. They responded with two approaches... the first matching what @NRielingh is saying. The second, and this is their suggested approach, is to use the new Actor System to send messages between Actors.
For my use case, here's how that looks...
Background
There's a parent machine responsible for spawning
Document Upload Machinesfor each file being uploaded.1. Spawn the Document Upload Machine
2. Pass Document Upload Machine
systemIdto actor3. Use
parent.sendto send to parent actor