Is there a way to initialize an Xstate state machine using a promise returned from Firebase?

Viewed 1357

I am trying to persist state using Firebase. I am currently using a 'saveState' function which works fine and properly saves the most recent state to Firebase.

Now I'd like to be able to initialize the state machine based on the most recent saved state in Firebase. In the code below I am trying to use my 'loadState' function to provide Xstate with a config object. It currently returns a promise with the correct state configuration within.

Here is my 'saveState' code:

 //This function works fine.
 function saveState(current, id){
        let transactionJSON = serialize(current);
        transactionJSON['createdOn'] = new Date();
        return firebase.saveTransactionState({transactionJSON, transactionId:id});
    }

Here is my 'loadState' function which returns a promise from Firebase with the correct config information within.

function loadState(id){
        return firebase.getTransactionState({transactionId:id}).then(function(querySnapshot) {
            return querySnapshot.docs.map(doc => deserialize({...doc.data()})  );
        });
    };

Now my issue is trying to load Xstate with the above 'loadState' function. Here I am trying to use a useMachine React hook:

const [current, send] = useMachine(transactionMachine,{
        state: () => loadState(id), //Trying to do something like this, but it doesn't seem to work.
        actions:{
            save: () => saveState(current, id),
        },
    });

I end up with the error: "TypeError: Cannot read property 'data' of undefined", which I believe is happening because the promise hasn't resolved yet leading to trying to read an undefined value.

Is this even possible or am I going about this all wrong?

I am new to all this, any guidance would be appreciated. Thank you.

1 Answers

I suppose you could use useEffect to update your state machine following your fetch.

How about invoking the fetch within the state machine?


import { Machine, assign } from 'xstate';


const yourMachine = Machine({

    id: 'yourStateMachine',
    initial: 'fetching',
    context: {
        values: {
            id: '', // likely need a state to initialize this value before fetching
            apiDat: {}
        },
    },
    states: {

        fetching: {
            invoke: {
                src: ({values}) => firebase
                    .getTransactionState({ transactionId: values.id })
                    .then(function(querySnapshot) {
                        return querySnapshot.docs.map(
                            doc => deserialize({...doc.data()})  
                        );
                    }),
                onDone: { target: 'resolving', actions: 'cacheApiDat' },
                onError: { target: 'error.fetchFailed' },
            }
        },

        resolving: {
            initial: 'delay',
            states: {
                delay: { 
                    after: { 
                        750: 'resolve' 
                    }, 
                },
                resolve: {
                    always: [
                        { cond: 'isSuccess', target: '#yourStateMachine.idle' },
                        { cond: 'isUnauthorized', target: '#yourStateMachine.error.auth' },
                    ],
                }
            },
        },

        error: {
            states: {
                fetchFailed: {
                    type: 'final',
                },
                auth: {
                    type: 'final',
                }
            }
        },

        idle: {

        },

    }



},{

    actions: {

         cacheApiDat: assign({ values: ({values}, event) => ({
                   ...values,
                   apiDat: event.data, // save whatever values you need here
              }),
         }),

    },

    guards: {
        // this requires a definition based on firebase's returned api call on a success
        isSuccess: ({values}) => typeof values.apiDat.data !== 'undefined',
        // this requires a definition based on firebase's returned api call when the call is unauthorized
        isUnauthorized: ({values}) => typeof values.apiDat.detail !== 'undefined' && values.apiDat.detail === 'invalid_auth',
    }

});

Related