Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v5] Restrict context to object #2294

Merged
merged 8 commits into from
Jun 13, 2021
Merged

[v5] Restrict context to object #2294

merged 8 commits into from
Jun 13, 2021

Conversation

davidkpiano
Copy link
Member

@davidkpiano davidkpiano commented Jun 10, 2021

This PR restricts context passed into createMachine(...) or createModel(...) to be an object.

Closes https://github.com/davidkpiano/xstate/projects/1#card-40598611

@changeset-bot
Copy link

changeset-bot bot commented Jun 10, 2021

🦋 Changeset detected

Latest commit: 5cd04ee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
xstate Major
@xstate/examples-fetch Patch
@xstate/graph Major
@xstate/immer Major
@xstate/inspect Major
@xstate/react Major
@xstate/svelte Major
@xstate/test Major
@xstate/vue Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@davidkpiano davidkpiano changed the base branch from main to next June 10, 2021 02:56
@VanTanev
Copy link
Contributor

VanTanev commented Jun 10, 2021

In typescript, object | undefined basically means "anything." All these pass:

type X = object | undefined

let a: X = []
let b: X = new Map()
let c: X = new String('a')
let d: X = new Number(1)
let e: X = new Number(NaN)
let f: X = new Boolean(false)
// ... etc

What is the restriction you're trying to apply?

@davidkpiano
Copy link
Member Author

In typescript, object | undefined basically means "anything." All these pass:

type X = object | undefined

let a: X = []
let b: X = new Map()
let c: X = new String('a')
let d: X = new Number(1)
let e: X = new Number(NaN)
let f: X = new Boolean(false)
// ... etc

What is the restriction you're trying to apply?

Only literal objects { ... } or undefined. I did this late at night 😅 Let me know if there's a better type for this.

@VanTanev
Copy link
Contributor

Unfortunately, as far as I know, there is no such type.
This is because TS has no concept of a hash/map literal as a separate type.

I think that usually Record<string, any> | undefined communicates the idea better, but all the above examples pass the same way. I /think/ that there were some esoteric cases where it provided slightly better results than object, but can't remember off the top of my head.

@Andarist
Copy link
Member

Yep, this is a very unfortunate (and quite frankly - misleading) quirk of TS. Some simple examples showcased here. I think the object reads best and since I'm not sure when Record would give us anything beyond object I think we can stick with it to keep things simple. And actually object might be better in some situations (see here)

I wonder though if we have to support undefined - or rather: what should be the result of this:

const m = createMachine<undefined>({})
m.context // {} or undefined?

I think that defaulting to an empty object could potentially lead to less special-casing certain code paths in the implementation and that there is no much value in supporting undefined contexts.

@davidkpiano davidkpiano changed the title [v5] Restrict context to object or undefined [v5] Restrict context to object Jun 13, 2021
${scope}
${body}
${scope}
with (context) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Asking for forgiveness for using the deprecated with statement, but ✨ it works so well

Comment on lines +206 to +210
it('machines defined without context should have a default empty object for context', () => {
const machine = createMachine({});

expect(machine.initialState.context).toEqual({});
});
Copy link
Member Author

Choose a reason for hiding this comment

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

❕ New test

@@ -239,7 +245,7 @@ describe('machine', () => {
bar: 42
});

expect(changedBarMachine.initialState.context).toBeUndefined();
expect(changedBarMachine.initialState.context).toEqual({ bar: 42 });
Copy link
Member Author

Choose a reason for hiding this comment

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

❕ Changed test outcome (context is no longer undefined ever)

Comment on lines +302 to +314
describe('types', () => {
it('defined context in createMachine() should be an object', () => {
createMachine({
// @ts-expect-error
context: 'string'
});
});

it('defined context passed to createModel() should be an object', () => {
// @ts-expect-error
createModel('string');
});
});
Copy link
Member Author

Choose a reason for hiding this comment

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

❕ New tests

@davidkpiano
Copy link
Member Author

Reviewers: you can safely ignore most of the changes in these files (there's a ton), because they're mostly changing the types to enforce that TContext is of type MachineContext.

Look for the comments with an ❕ icon.

davidkpiano and others added 3 commits June 13, 2021 10:23
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
@davidkpiano davidkpiano merged commit 4b32de7 into next Jun 13, 2021
@Andarist Andarist deleted the v5/context-merging branch March 22, 2023 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants