Suggestion: Less awkward way to pass context and event types
devanshj opened this issue ยท 5 comments
useStateMachine is a curried function (Yummm tasty!) because TypeScript doesn't yet support partial generics type inference. This workaround allows TypeScript developers to provide a custom type for the context while still having TypeScript infer all the types used in the configuration (Like the state & transitions names, etc...).
We no longer have this limitation with #36. We can have something like the following for context...
useStateMachine({
initial: "idle",
context: { foo: 1 } // I prefer "initialContext" but whatever xD
...
})
And something like this (what xstate v5 does), for events and context...
useStateMachine({
schema: {
context: createSchema<{ foo: number, bar?: number }>(),
event: createSchema<
| { type: "X", foo: number }
| { type: "Y", bar: number }
>(),
// I prefer "event" instead of "events" because the type is for event and not "events"
// (unions need to be named singular even though they seem plural)
// though "events" works too if that's more friendly
},
initial: "idle",
context: { foo: 1 }
...
})
@cassiozen Waiting for your thoughts on this before I continue working on the PR because if you like this idea and we'd be going with this then will have to redo all the work :P
Oh, that looks pretty good. Let's go with it.
This schema was taken as is it from xstate, I personally don't like it for many reason stated here, it probably will change in xstate too (I'm speaking with David, let's see). Here's a hypothetical narrative to explain the gist.
A: I don't like the fact that if you have 10 events and only one has a payload you have to write all of them instead of defining the payload just for one
A: Let's make it...
events: {
ON_CHANGE: t<{ value: string }>()
}
A: ...so that'll mark ON_CHANGE
has a payload of { value: string }
and rest events (eg ON_BLUR
) that will be inferred from on
will have no payloads
B: Hmm... But what if someone makes a typo and the event with a typo gets inferred. For example it user makes a typo ON_BLURR
instead of ON_BLUR
it won't get caught even if I define it in the schema as events: { ON_BLUR: {} }
because we're merging schema and inferred.
A: Okay then let's have...
events: {
types: ["ON_CHANGE", "ON_BLUR"],
// in case of `types: undefined` they are inferred
payloads: {
// user can have `payloads` even if they didn't define `types` because they will be inferred
ON_CHANGE: t<{ value: string }>()
// the events missing are assumed to have no payload
// meaning writing `ON_BLUR: {}` would have the same effect
}
}
A: ...in this way people who don't care about typos can simply skip writing types
and those who care can write them all.
B: Hmm looks good
So I'm proposing we make it schema: { events: { types, payloads } }
and if you think no one cares about typos we can make it schema: { events }
(the first snippet) too :P
Feel free to ask questions if I didn't explain myself clearly!
Edit -
I have a better version see "edit 2" section of this comment. Copying it here for reference.
Okay so I figured out a way to make the api simpler at the same time solving the problems. Here's a narrative explanation of how the current api came into being, and here's a simpler version
guards: { $$exhaustive: true isEnterKey: t<(_: never, event: { keyCode: number }) => boolean>(), isShiftKey: t.constraint<(_: never, event: { keyCode: number }) => boolean>() isFoo: t.inferred() }Now instead of writing all identifiers they can just set
$$exhaustive: true
(we can even export a symbol if we don't want to reserve "$$exhaustive" as an identifier). Alternatively if we want the default behavior to be exhaustive users can set$$exhaustive: false
instead when wanting other identifiers to be inferred.
So in our case the final schema would look something like...
context: t<{ foo?: number }>(),
events: {
$$exhaustive: true,
ON_CHANGE: t<{ value: string }>(),
ON_BLUR: t<{}>()
}
And if the user doesn't care about typos then...
context: t<{ foo?: number }>(),
events: {
ON_CHANGE: t<{ value: string }>()
}
I really like this!
Cool so I'll update the PR soon and it'll be good to merge!