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

req: Allow access to internal service #9

Open
lxcid opened this issue Jun 24, 2024 · 6 comments · Fixed by #10
Open

req: Allow access to internal service #9

lxcid opened this issue Jun 24, 2024 · 6 comments · Fixed by #10

Comments

@lxcid
Copy link

lxcid commented Jun 24, 2024

Is it possible for services to be exposed or accessible.

This allow us to hook up listeners post initialization.

@dai-shi
Copy link
Member

dai-shi commented Jun 24, 2024

@brunocangs any thoughts?

@brunocangs
Copy link
Collaborator

brunocangs commented Jun 24, 2024

We have the actor/service available on the internal machineAtom variable, we could look for a way to expose access to it.

I'm not very familiar with the Jotai internals, what would be the correct way to expose this? My initial thought was to add a symbol somewhere so we could consume it from another atom. So we could do something like

const machineAtom = atomWithMachine(...)
const actorAtom = actorFromMachine(machineAtom)

If there was some way to consume the internal machineAtom from an external atom it would also work. Maybe keep an external store/atom and read from it?

@lxcid What you need is to be able to call the .on() listeners1, correct?

Edit: Thinking about it some more, the best way would probably be to decouple the logic, and make each piece along the setup process available to consume separately. That way we don't lose compatibility with the current API.

  • atomWithLogic => Receives Actor logic23 (eg: createMachine(), fromPromise()) to setup the instance of the logic. Write function could be used to provide new init arguments.
  • atomWithActor => Received either the above atom, or an instanced actor logic. This atom would call createActor and make it available. This solves the issue by giving direct access to the actor. For the Write function, I think it should default to the send method, but it could also be used to call .start() and .stop() using symbols.
  • atomWithActorState => Handles subscription and has the latest snapshot data. Takes an actor or atomWithActor as first argument and an optional second argument 4 to go along with the internal subscribe() call
  • atomWithMachine => Keeps the same api, but consumes the previous atoms to easily instantiate and send events into a state machine or generic actor

If you think this is a good path, I can look for some time during this week to try to implement it

Footnotes

  1. https://stately.ai/docs/event-emitter

  2. https://stately.ai/docs/actors#actor-logic-creators

  3. https://www.jsdocs.io/package/xstate#ActorLogic

  4. https://www.jsdocs.io/package/xstate#Actor.subscribe

@lxcid
Copy link
Author

lxcid commented Jun 24, 2024

thank you for considering. generally. a machine is like a configuration and an actor is an instance of the configuration and store the state.

sometimes we want to listen to state transition, thus the on() call or onTransition()

think of machine instance as some kind of observable. able to receive event and changes accordingly.

if i want i can listen to changes to state, but i thought naturally it might make sense to listen to transition from the machine instance.

your proposal sounds great. if i can control each step of the step, i can have more control over it.

@rmarscher
Copy link

I've been building a global shopping cart machine for an app and was having issues with reactivity when an event triggered a state change of my machine. My components were not re-rendering. I need to try to put together a simple reproduction of that issue. It may have to do with the state also invoking an actor.

Anyway... while looking at the code here and reading @xstate/react, I decided to try an alternate implementation that seems to be working for me that I called atomWithActorLogic:
https://gist.github.com/rmarscher/2676b2b661c7119e7dd2a9449c179718

I took inspiration from @xstate/react's createActorContext code. It uses useActorRef to create an actor and start it. It then uses useSelector within a component to add a snapshot listener that triggers updates.

I added a useSnapshot convenience hook that just returns the entire snapshot from useSelector.

So instead of component code like this:

const [state, send] = useAtom(machineAtom);

It works like this with an atom created with useActorWithLogic:

const [actorRef] = useAtom(machineAtom);
const state = useSnapshot(machineAtom);

Instead of send({ type: "some.event" }), it's actorRef.send({ type: "some.event" }). And you can do whatever you want with actorRef in your component too.

@rmarscher
Copy link

rmarscher commented Oct 30, 2024

Sorry -- I know this is off-topic for this issue, but just to follow up to my previous post -- I found a similar issue with useSelector not triggering rerenders.

My states with an invoked actor are calling a server function in React 19 (with Waku). One of the actors works fine but the other seems to not allow react to update until the server function is finished being called and that seems to be preventing the snapshot update. I wasn't able to reproduce with a simpler example with just xstate and client side react. I need to keep working on that (and will open another issue here or upstream if I can track it down). I did find a hack to workaround the issue - using setTimeout with a 0 timeout and calling my server function within the callback function:

const res = await new Promise<
  Awaited<ReturnType<typeof serverFunction>>
>((resolve) =>
  setTimeout(async () => {
    console.log("sending server function request");
    const _res = await serverFunction(params);
    console.log("finished server function request");
    resolve(_res);
  }, 0)
);

When I invoke my server function within my fromPromise(...) actor that way, the snapshots update and my components are reacting to the state changes.

@brunocangs
Copy link
Collaborator

@lxcid @rmarscher We've just released v0.6.0 with lots of changes and improvements, would you be able to test against it and see if the problems persist?

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 a pull request may close this issue.

4 participants