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

Parameters when transitioning states #29

Closed
jonathanj opened this issue May 24, 2021 · 3 comments
Closed

Parameters when transitioning states #29

jonathanj opened this issue May 24, 2021 · 3 comments
Labels

Comments

@jonathanj
Copy link

jonathanj commented May 24, 2021

It looks like there is currently no way to provide external information when transitioning states, I read the source code for send, and the examples (such as fetch) seem to internalise the external effect so that the data is available within effect. Apologies if I did a poor job of looking.

My use case is moving data from a subscription callback into the state machine. I need to manage an external lifecycle (add/remove subscriber), and data is supplied to me via callback to the subscriber. Firebase's realtime database is a concrete example:

const [state, send] = useStateMachine({firebaseValue: })({
  initial: 'loading',
  states: {
    loading: {
      on: {
        SUCCESS: 'loaded',
        FAILURE: 'error',
      },
      effect(_, update, /* ??? */) {
        update(context => /* External value would be placed into the context here. */)
      },
    },
    // …
  },
})

React.useEffect(() => {
  // A hypothetical `send` API with parameters.
  const success = value => send('SUCCESS', value)
  const failure = error => send('FAILURE', error)
  const ref = firebase.database().ref('some/path')
  // Manage the subscriber lifecycle.
  ref.on('value', success, failure)
  return () => {
    ref.off('value', success)
  }
)

I realise it would be possible to achieve this with a ref, but I would prefer not having one foot in the door in terms of state and context.

@cassiozen
Copy link
Owner

cassiozen commented May 24, 2021

Hi, good question. This is by design - Ideally, changing the state machine context is always a consequence of the state machine itself transitioning. It should never come from the outside.

My suggestion is, as you noted, internalise the effect:

const [state, send] = useStateMachine({firebaseValue: })({
  initial: 'loading',
  states: {
    loading: {
      on: {
        SUCCESS: 'loaded',
        FAILURE: 'error',
      },
      effect(send, update) {
        const success = firebaseValue => {
          update(context => ({firebaseValue, ...context}))
          send('SUCCESS')
        }
        const failure = error => {
          update(context => ({error, ...context}))
          send('FAILURE')
        }
        const ref = firebase.database().ref('some/path')
        // Manage the subscriber lifecycle.
        ref.on('value', success, failure)
        return () => {
          ref.off('value', success)
        }
      },
    },
    // …
  },
})

But this will unsubscribe as soon as the first piece of data is fetched. Another strategy would be using nested states, which this library doesn't support yet (working on it). A nested state would allow you to do something like this:

const [state, send] = useStateMachine({firebaseValue: })({
  initial: 'subscribe',
  states: {
    subscribe: {
      initial: 'loading',
      on: {
        SUCCESS: 'active',
        FAILURE: 'error',
      },
      states: {
        loading: {},
        active: {},
        error: {}
      },
      effect(send, update) {
        const success = value => {
          update(context => ({value, ...context}))
          send('SUCCESS')
        }
        const failure = error => {
          update(context => ({error, ...context}))
          send('FAILURE')
        }
        const ref = firebase.database().ref('some/path')
        // Manage the subscriber lifecycle.
        ref.on('value', success, failure)
        return () => {
          ref.off('value', success)
        }
      },
    },
    // …
  },
})

This would start as subscribe > loading, then would either transition to subscribe > error or subscribe > active. The effect on the parent "subscribe" state would still be active, sending updates.

Right now, I would suggest using XState for this, and keep an eye on the progress on #21

@jonathanj
Copy link
Author

Thanks for the clear and detailed reply, @cassiozen!

@cassiozen cassiozen reopened this May 24, 2021
@cassiozen
Copy link
Owner

Closing for now, please feel free to reopen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants