1

I'm look at implementing a loading screen that checks if all components are loaded, but came across this weird pattern. With the code below, I get App false in console to start, after 2000ms when the delayHTML function returns back data, I get LoadComponent mount event and App true.

But then it goes back into the suspense fallback, and I get a further 2000ms where the LoadComponent mount event and App true messages appear again, in what looks like a 2nd render. How can I avoid that?

function App() {
  const [fullLoadState, setfullLoadState] = useState(false);
  
  console.log("App " + fullLoadState)

  eventBus.on("FullyLoaded", (data) => setfullLoadState(true));

  return (
    <div>
      <Suspense fallback='Fallback'>
        <LoadComponent resource={delayHTML(2000)}/>
      </Suspense>
      <p>{fullLoadState ? null : "Site not fully loaded" }</p>
    </div>
  );
}

function LoadComponent({resource}){
  useEffect(()=> {
    console.log ('LoadComponent mount event')
    eventBus.dispatch("FullyLoaded", {messsage: 'some message'});
  })
  const html = resource.read();
  return html;
}

export default App;

EventBus is just EventListener. delayHTML suspends until it reaches 2000ms to return a text value.

David Min
  • 1,246
  • 8
  • 27
  • 1
    Both messages are expected behaviour. Changing state causes component (App) rerender. `useEffect` w/o dependencies also runs on every render. – Yury Tarabanko Aug 11 '20 at 11:01
  • 1
    Also you should never perform side-effects (like subscriptions) in render. Move this code to `useEffect` with proper clean-up function. `eventBus.on("FullyLoaded", (data) => setfullLoadState(true));` – Yury Tarabanko Aug 11 '20 at 11:05

2 Answers2

1

Based on the way how react renders its component, you can create a variable, assign delayHTML and use that.

For example,

const delayFunc = delayHTML(2000);
...
<LoadComponent resource={delayFunc}/>

This should work.

Hayden S.
  • 742
  • 4
  • 10
  • Tried a variation of this - const delayFunc = useState(delayHTML(2000)). This somewhat worked - there's no longer a 2nd loading for the suspense fallback, but `LoadComponent mount event` was logged twice in console. There is also an warning in console: `Cannot update a component (`App`) while rendering a different component (`LoadComponent`)` – David Min Aug 11 '20 at 12:04
  • Can you share delayHtml function body? – Hayden S. Aug 11 '20 at 12:51
  • I think Yury may be right - state changes causes React to trigger the render() function again. function delayHTML(ms) { return (wrapPromise(new Promise(resolve => { setTimeout(() => { resolve('abc') }, ms ) }))) } – David Min Aug 11 '20 at 17:56
  • Yes, state changes cause React to trigger render method again. This is how React is designed. – Hayden S. Aug 11 '20 at 18:01
  • Thanks! Upvoted but it doesn't show here. Any ideas on the warning message? It doesn't affect anything but might not be the React way to do things. – David Min Aug 12 '20 at 09:38
  • I actually don't understand why you use eventBus. You can pass handler as props, which is a more easy and smart way. – Hayden S. Aug 12 '20 at 09:48
  • Also, add [] to useEffect func. – Hayden S. Aug 12 '20 at 11:04
  • Yep removed event bus in the final implementation - it was just shorthand for dev. Still has that error message though. – David Min Aug 12 '20 at 15:07
0

Can you try and change your useEffect to this:

  useEffect(()=> {
    console.log ('LoadComponent mount event')
    eventBus.dispatch("FullyLoaded", {messsage: 'some message'});
  }, [])
nir shabi
  • 367
  • 1
  • 15