callObject.join throws a "Cannot read properties of null (reading 'start')" exception

zarally
zarally Member

Hey all - i have a recoil app that hoists the DailyProvider in two files. The first is main.jsx, which loads the RecoilRoot inside a Meteor startup:

import React from 'react'
import { Meteor } from 'meteor/meteor'
import { render } from 'react-dom'
import { RecoilRoot } from 'recoil'
import App from '../imports/ui/App'

Meteor.startup(() => {
  render(
    <RecoilRoot>
      <App />
    </RecoilRoot>,
    document.getElementById('react-target')
  )
})

Next, the App.jsx loads the DailyProvider like this:

import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import DailyIframe from '@daily-co/daily-js'
import { useSetRecoilState } from 'recoil'
import { DailyProvider } from '@daily-co/daily-react'
import { callObj } from './recoil/atoms'
import Main from './Main'

export default function App() {
  const callOpts = {
    dailyConfig: {
      v2CamAndMic: true,
    },
  }
  const setCallObj = useSetRecoilState(callObj)
  const co = DailyIframe.createCallObject(callOpts)
  setCallObj(co)

  return (
    <DailyProvider
      callObject={co}
      recoilRootProps={{
        // stores Daily React's state in RecoilRoot above
        override: false,
      }}
    >
      <Router>
        <Main />
      </Router>
    </DailyProvider>
  )
}

And finally, when I make this call deeper in the app:

const callObject = useRecoilValue(callObj)
const joinResult = await callObject.join({ url: `${BASE_DAILY_URL}/${roomId}`, token: joinToken, })

And I am getting the below exception from the daily library:

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'start')
at e.value (modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50669:16116)
at modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50669:79332
at new Promise (<anonymous>)
at z.<anonymous> (modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50669:79256)
at Generator.next (<anonymous>)
at t (modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50663:3742)
at s (modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50663:3945)
at modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50663:4004
at new Promise (<anonymous>)
at z.<anonymous> (modules.js?hash=5af7ca3d8ce672432ea2e8d9a93c030b0148f7ab:50663:3885)

the object on which start is being called is on this line:

this._currentLoad.start()

When I didn't store the callObject in recoil, and I created it in the same file as the callObject.join call, then this exception wasn't throwing. The problem is I'm trying to use the callObject elsewhere. Whether using useDaily or useRecoilValue(callObj) doesn't seem to matter - this exception is always being thrown.

Any ideas?? Thanks!

Best Answer

  • christian
    christian Dailynista
    Answer ✓

    @zarally Daily React internally stores the callObjectin a React state using useState() and passes it down to other components through a React context. Have you considered storing your own callObject in a React state instead of a recoil atom? It's possible that a callObject instance doesn't match all requirements for a recoil atom, because the callObject is not immutable. If you need to store it in an atom, you could try setting the dangerouslyAllowMutability flag on the atom, but otherwise I'd recommend storing it in a simple React state, as it's proven to work in Daily React internally, and access the instance with useDaily() inside of your application.

    Let me know if that helps!

Answers

  • christian
    christian Dailynista

    Hey @zarally,

    I'm not super familiar with Meteor, but looking at the provided code through a React lens, I would highly suggest to not create the callObject as part of the render phase in the App component.

    Instead you'll want to create it inside of a useEffect and instead of passing the "just created reference to the call object" to DailyProvider, you'll probably want to pass the recoil value.

    Here's a suggested update to your App component:

    import React, { useEffect } from 'react'
    import { BrowserRouter as Router } from 'react-router-dom'
    import DailyIframe from '@daily-co/daily-js'
    import { useRecoilValue, useSetRecoilState } from 'recoil'
    import { DailyProvider } from '@daily-co/daily-react'
    import { callObj } from './recoil/atoms'
    import Main from './Main'
    
    const callOpts = {
      dailyConfig: {
        v2CamAndMic: true,
      },
    }
    
    export default function App() {
      const co = useRecoilValue(callObj);
      const setCallObj = useSetRecoilState(callObj);
    
      // Initialize 
      useEffect(() ⇒ {
        // callObject already created and stored in Recoil
        if (co) return;
            
        // Check if callObject was already created
        let _co = DailyIframe.getCallInstance();
        if (!_co) {
          // Create callObject if necessary
          _co = DailyIframe.createCallObject(callOpts);
        }
        // Store callObject in Recoil
        setCallObj(_co);
      }, [co, setCallObj]);
    
      return (
        <DailyProvider
          callObject={co}
          recoilRootProps={{
            // stores Daily React's state in RecoilRoot above
            override: false,
          }}
        >
          <Router>
            <Main />
          </Router>
        </DailyProvider>
      )
    }
    

    Let me know if this improves the situation for you!

  • Thanks! I will try this. In the interim I did end up having to put the callObj on windowand not in recoil, which is suboptimal.

  • I tried the same <App> configuration you suggested (in the useEffect hook but not in the render), but unfortunately that throws the same error.

        "@daily-co/daily-js": "^0.50.0",
        "@daily-co/daily-react": "^0.11.6",
    

    I'm tried using both const callObject = useRecoilValue(callObj) and const callObject = useDaily() — both work to retrieve the callObject, but neither work when I attempt to join the room, throwing the above error.

    I've tried with and without v2CamAndMic: true and it doesn't seem to matter.

    Instead, if I assign callObject to window in the <App> — window.callObject = callObj then use window.callObject.join — it joins the room without throwing the exception…

  • Interestingly, calling setCallObj(_co); is what seems to mess up the object - I pass it by reference to the global object window.callObject = _co— and the join call only works if I do NOT setCallObj(_co) first. (I realize i wouldn't need to save the callObject in both places, I was just trying to discover what causes the object to be unable to make the join call)

  • Hi @christian - that worked, thanks! I was able to use store the callObject in just the React.state, NOT in recoil, as you're suggesting. I did have to remove the below as well. Seems like it should be documented for now — using this in the <DailyProvider>seems misleading, as it can't truly story the callObject in recoil.

    recoilRootProps={{
      // stores Daily React's state in RecoilRoot above
      override: false,
    }}
    

    Thanks so much for your help.