cassiozen / useStateMachine

The <1 kb state machine hook for React
MIT License
2.38k stars 47 forks source link

Make "creating" and "using" a machine seperate #79

Open devanshj opened 2 years ago

devanshj commented 2 years ago

The fact that useMachine “creates” the machine means that we can only create a machine inside an component. This problematic because...

  1. You can't define a mahine outside the component (eg)
  2. You can't get the type of the (created) machine outside the component (which is required for context, eg)

createDefinition solves 1 but doesn't solve 2 so we need something like xstate ie let m = createMachine(d); then later in component useMachine(m). We could add an overload to do useMachine(d) which saves users from writing useMachine(createMachine(d))

devanshj commented 2 years ago

And also that means that users would "need" the second argument of useMachine too for changing things inside the component etc. – which I'm super strongly against – there's no point of reinventing functions people can just const createFooMachine = (foo: Foo) => createMachine(...). Not to mention the "named delegations" ie writing "foo" in xstate are problematic for typing because we don't know their types, here we know the type it's Foo so for us it's as if the user is writing the implementation there.

cassiozen commented 2 years ago

I love the idea but I don't think this is super-priority given that most use cases for this library (so far) are for small, in-component state machines.

devanshj commented 2 years ago

Yeah I wouldn't call it "super priority" but it sure makes it impossible for it to be able to use it with React's context as the type isn't available outside the component (and you create the context via React.createContext() outside the component)

devanshj commented 2 years ago

Just putting this out because I had an idea long ago how to implement this without moving away from React or changing the implementation. It's something like this...

const createMachine = (definition) => () => {
  // same implementation
}

const useMachine = (definitionOrHook) => {
  if (typeof definitionOrHook === "function") {
    return definitionOrHook()
  }
  return createMachine(definitionOrHook)()
}

Now the machine (or more precisely the hook to use it) can be created outside the component and hence we can get its type for context too...

const useMyMachine = createMachine(...)
type MyMachine = ReturnType<typeof useMyMachine>

const MyMachineContext = React.createContext<MyMachine>()
const SomeComponent = () => {
  let machine = useMyMachine()

  return <MyMachineContext.Provider value={machine}>
    {/* ... */}
  </MyMachineContext.Provider>
}
erickreutz commented 1 year ago

Would love to see this!