title: React: ref to child component is null after remounting by changing key
body: <p>For a 1v1 Sudoku game, my <code>GamePage</code> component renders the main <code>Game</code> component, which contains a <code>Clock</code> for each player. When both players agree to a rematch, the entire <strong><code>Game</code> is reset by simply incrementing its <code>key</code> by 1</strong> (after changing the GamePage state to reflect the settings of the new game).</p>
  <p><strong>My Problem:</strong><br>
  <code>Game</code> stores two refs <code>this.myClock</code> and <code>this.opponentClock</code> to the countdowns inside of both clocks, so they can be paused/started when a player fills a square. This works perfectly fine for the first game. However, <strong>after <code>Game</code> remounts, any move will throw &quot;Cannot read properties of null (reading 'start')&quot; at e.g. <code>this.opponentClock.current.start()</code>.</strong></p>
  <p>I know that refs are set to null when a component unmounts, but by rendering a new version of <code>Game</code>, I would expect them to be set in the constructor again. To my surprise, the new timers are set correctly and one of them is running (which is also done in <code>componentDidMount</code> of <code>Game</code> using the refs), but any access afterwards breaks the app.</p>
  <p>I would be incredibly grateful for any tips or remarks about possible causes, I've been stuck on this for two days now and I'm running out of things to google.</p>
  <p><strong>GamePage.js:</strong></p>
  <pre><code>export default function GamePage(props) {
      const [gameCounter, setGameCounter] = useState(0) //This is increased to render a new game
      const [gameDuration, setGameDuration] = useState(0)
      ...
      useEffect(() =&gt;{
          ...
          socket.on('startRematch', data=&gt;{
              ...
              setGameDuration(data.timeInSeconds*1000)
              setGameBoard([data.generatedBoard, data.generatedSolution])
              setGameCounter(prevCount =&gt; prevCount+1)
          })
      },[]) 
      
      return (
          &lt;Game key={gameCounter} initialBoard={gameBoard[0]} solvedBoard={gameBoard[1]} isPlayerA={isPlayerA} 
          id={gameid} timeInMs={gameDuration} onGameOver={handleGamePageOver}/&gt; 
      )
  }
  </code></pre>
  <p><strong>Game.js:</strong></p>
  <pre><code>class Game extends React.Component {
      constructor(props) {
          super(props);
          this.state = {
              gameBoard: props.initialBoard, 
              isPlayerANext: true,
              gameLoser: null, //null,'A','B'
          };
          this.myClock = React.createRef(); 
          this.opponentClock = React.createRef();
      }
  
      componentDidMount(){
          if(this.props.isPlayerA){
              this.myClock.current.start()
          }
          else{
              this.opponentClock.current.start()
          }
          socket.on('newMove', data =&gt;{
              if(data.isPlayerANext===this.props.isPlayerA){
                  this.opponentClock.current.pause()
                  this.myClock.current.start()
              }
              else{
                  this.opponentClock.current.start()
                  this.myClock.current.pause()
              }
          })
          ...
      }
      
      render(){
          return( 
          &lt;React.Fragment&gt;
              &lt;Clock ref={this.opponentClock} .../&gt;
              &lt;Board gameBoard={this.state.gameBoard} .../&gt;
              &lt;Clock ref={this.myClock} .../&gt;
          &lt;/React.Fragment&gt;)
          ...
      }
  }
  
  export default Game
  </code></pre>
  <p><strong>Clock.js:</strong></p>
  <pre><code>import Countdown, { zeroPad } from 'react-countdown';
  
  const Clock = (props,ref) =&gt; {
      const [paused, setPaused] = useState(true);
      return &lt;Countdown ref={ref} ... /&gt;
  }
  
  export default forwardRef(Clock);
  </code></pre>
  <p>Edit:
  Accepted answer works like a charm. The problem was not the new ref per se, but that <code>socket.on('newMove',...)</code> and <code>socket.on('surrender',...)</code> using the old refs were not cleaned up properly on unmount of the old game.</p>