How to make sure an `useImperativeHandle` ref isn't filled until another ref is filled?

235 Views Asked by At

tl;dr

I'd like to enforce the following invariant: when using useImperativeHandle, don't fill the ref until some other ref is filled.

Long version

I have weird components

I have a component that uses a custom function for creating a <canvas>. This function knows nothing about React and comes from an external library. It returns a methods object that can be used to interact with the canvas.

function createGame(...): GameMethods {...}

I also have a component (NextReactP5Wrapper) that calls this createGame function in a special way and manages the created <canvas>

function Game(props) {
  return (
    <NextReactP5Wrapper
      sketch={(p5) => {
        const methods = createGame(props.env, p5)
        return
      }}
    />
  )
}

// Note: React doesn't guarantee that `memo` will not rerender.
// But so far it works, and I haven't found any other way.
const MemoizedGame = React.memo(Game, () => true)

What I want to do

I'd like to make the returned methods to be available wherever I'm using Game. So I'm using forwardRef and useImperativeHandle:

const Game = React.forwardRef<GameMethods, { env: GameAttributes }>(
 function Game(props, ref) {
  const methodsRef = React.useRef<GameMethods | null>(null)
  React.useImperativeHandle(ref, () => ({
    // thanks to 'current?' we get a potential null here 
    getFoo: () => methodsRef.current?.getFoo(),
  }))
  return (
    <NextReactP5Wrapper
      sketch={(p5) => {
        const methods = createGame(props.env, p5)
        gameMethods.current = methods
      }}
    />
  )
})

const MemoizedGame = React.memo(Game, () => true)

...elsewhere in my <Page> component
const gameRef = React.useRef<GameMethods>(null)
return <Game ref={gameRef}/>

My problem

Now I have a problem: gameRef.current can be null and the result of gameRef.current.getFoo() can also be null!

What I want

I'd like to make sure the imperative handle ref isn't filled until the result of createGame() is available. Then I can have an easier life, knowing that if gameRef.current !== null, I can call of its methods.

Is it possible?

Or should I just write gameMethods.current! everywhere I was writing gameMethods.current? and hope that everything will be fine?

0

There are 0 best solutions below