React router component render a very wrong state

Viewed 46
import React, { useEffect, useState } from 'react'
import { Route } from 'react-router-dom'
import { BrowserRouter as Router } from 'react-router-dom'

export default function Test() {
  const s = useS()
  if (s === 'aa') {
    return null
  }
  return (
    <Router>
      <Route component={MyCpt}></Route>
    </Router>
  )
}

function MyCpt() {
  const s = useS()
  return <h1>{s}</h1>
}

function useS() {
  const [state, setState] = useState('aa')
  useEffect(() => {
    subscribe(setState)
  }, [])
  return state
}

const listeners = []
function subscribe(cb) {
  listeners.push(cb)
}

setTimeout(() => {
  listeners.forEach((cb) => {
    cb('bb')
  })
}, 2000)

Here is totally runnable code, simply yet wired. As you can see, MyCpt will render the state from useS will eventually be bb (after 2 seconds). However, it will render aa no matter how long you wait. if you comment out return null inside the if (s === 'aa'), it will render bb as expect

What the hack is happening?

1 Answers

If you use some logs (code below), it will be clear. Look at the console.log:

1. Test aa 
2. Subscribed 
3. Running listeners 1 
4. Test bb 
5. MyCpt aa 
6. Subscribed 

Steps 1-2) What happens is first "Test aa" is logged, and subscription happens inside the hook, but due to returning null, the component exits.

Steps 3-4) Afterwards, the listener which was registered (note so far only one listener is registered, see count 1) is run, hence the output "Running listeners 1", and again "Test bb" is printed from component Test.

Steps 5-6) Now, MyCpt is being run, and subscription happens, but the listener is not being called again.


Here is code with logs:

import React, { useEffect, useState } from "react";
import { Route } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";

export default function Test() {
  const s = useS();
  console.log("Test", s);
  if (s === "aa") {
    return null;
  }
  return (
    <Router>
      <Route component={MyCpt}></Route>
    </Router>
  );
}

function MyCpt() {
  const s = useS();
  console.log("MyCpt", s);

  return <h1>{s}</h1>;
}

function useS() {
  const [state, setState] = useState("aa");
  useEffect(() => {
    subscribe(setState);
  }, []);
  return state;
}

const listeners = [];
function subscribe(cb) {
  console.log("Subscribed");
  listeners.push(cb);
}

setTimeout(() => {
  console.log("Running listeners", JSON.stringify(listeners.length));
  listeners.forEach((cb) => {
    cb("bb");
  });
}, 2000);
Related