有没有办法使用从 Firebase 返回的承诺来初始化 Xstate 状态机?

Posted

技术标签:

【中文标题】有没有办法使用从 Firebase 返回的承诺来初始化 Xstate 状态机?【英文标题】:Is there a way to initialize an Xstate state machine using a promise returned from Firebase? 【发布时间】:2021-06-07 00:00:54 【问题描述】:

我正在尝试使用 Firebase 保持状态。我目前正在使用“saveState”功能,该功能可以正常工作并将最新状态正确保存到 Firebase。

现在我希望能够根据 Firebase 中最近保存的状态来初始化状态机。在下面的代码中,我尝试使用我的“loadState”函数为 Xstate 提供一个配置对象。它目前返回一个带有正确状态配置的承诺。

这是我的“保存状态”代码:

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

这是我的“loadState”函数,它从 Firebase 返回一个带有正确配置信息的承诺。

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

现在我的问题是尝试使用上述“loadState”函数加载 Xstate。在这里,我尝试使用 useMachine React 钩子:

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),
        ,
    );

我最终得到错误:“TypeError: Cannot read property 'data' of undefined”,我认为这是因为承诺尚未解决,导致尝试读取未定义的值。

这有可能吗,还是我做错了?

我对这一切都很陌生,任何指导将不胜感激。谢谢。

【问题讨论】:

这里一样,谁知道答案 您无法使用异步获取的值初始化状态,因为 javascript 是单线程的,因此如果您以某种方式等待该值从 Firebase 返回,您的整个应用程序将完全冻结,直到它返回- 不好。只需提供某种虚假或默认的初始状态并在您的 React 代码中处理它,然后渲染加载微调器/消息,或者只是根据数据是否已获取来有条件地渲染组件。 @Jayce444,是的,感谢您的评论。我只是认为使用 Xstate 反应钩子可能有一些很酷的方法(因为我可能没有正确理解他们的文档),好像 Promise 本身就是状态机。 【参考方案1】:

我想您可以使用useEffect 在获取后更新您的状态机。

在状态机中调用 fetch 怎么样?


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',
    

);

【讨论】:

感谢您的回复。这会很好,除非我不确定如何将 firebase 上下文直接引入状态机,因为我将状态机放在单独的文件中。我使用const firebase = useContext(FirebaseContext); 来获取firebase 上下文并访问.getTransactionState 函数。我想知道是否有另一种方法可以将 firebase 上下文直接传递到状态机?当我尝试在状态机文件中定义 firebase 时,我可以理解地得到“无法在顶层调用 React Hook “useContext”。” 您可以在状态机上创建一个“init”状态并在状态机上下文中设置一个值firebase。接下来,当组件初始化时,您创建一个useEffect,它将检查您在状态机中的firebase 值。如果未设置,则从您描述的 useContext 调用中更新它。现在状态机有了你的 firebase 值,转换到获取! 您能否澄清“当组件初始化时,您会创建一个 useEffect 来检查您在状态机中的 firebase 值”?我正在尝试实现您的代码,但我在我的useMachine 钩子中使用asEffect,例如:actions: load: asEffect((context, event)=>context.firebase = firebase; context.transactionId = loadedState.id; )。我现在收到“TypeError: Cannot read property 'getTransactionState' of null”,大概是因为我的状态机在我的钩子访问它之前正在运行。 那太好了。实际上,我根据我的代码和您的建议整理了一个小沙箱,以帮助说明我的意思。希望它有所帮助。随意更改您需要的任何内容。基本上在 App.js 第 30 行是我试图将 firebase 上下文发送到我的 stateMachine.js 文件的地方。 codesandbox.io/s/crazy-mendel-te0dl?file=/src/App.js 抱歉回复太晚了。我并没有真正让它发挥作用,现在,我最终做了@Jayce444 提到的事情。我从父组件中的 firebase 获取 machineConfig 并将结果作为道具传递给子组件(我在其中使用 useMachine 钩子)。在那里我检查machineConfig prop 是否存在,如果存在,则通过setState 钩子将当前状态与useEffect 一起发送到父组件,该钩子也传递给子组件。如果machineConfig 不存在,则初始化为机器的初始状态。我也会尽快发布答案。

以上是关于有没有办法使用从 Firebase 返回的承诺来初始化 Xstate 状态机?的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 Firebase Observable 承诺不会返回任何东西

Firebase“一次”事件不返回承诺

有没有办法从 Firebase 云函数中的单个函数返回 2 个值并将它们存储在数据库中的不同节点中?

Firebase 函数不返回数据

Firebase 可调用函数冷启动

Firebase 查询后返回 Promise