title: React, how to update a state without repainting, or how to trigger useEffect without useState (and with useRef), or how to stop infinite loop
body: <h2>The situation</h2>
  
  <ul>
  <li>I want to redistribute a grid based on the height of the images (using a masonry approach)</li>
  <li>Those images are lazy loaded</li>
  </ul>
  
  <h2>What I want</h2>
  
  <ul>
  <li>When the images are loaded, they send a flag, this flag makes the masonry logic to redistribute the grid, and everybody is happy</li>
  <li>Then when the user scrolls, more images are loaded, trigger the flag again and masonry again adjusts the grid</li>
  </ul>
  
  <h2>What happens</h2>
  
  <p>* <code>const [myState, setMyState] = use State(0)</code></p>
  
  <ul>
  <li>When the images are loaded I <code>setMyState(1)</code></li>
  <li>In masonry logic, I have a <code>useLayoutEffect(() =&gt;{}, [myState])</code> so when the state changes, I adjust the grid</li>
  <li>Then I reset the state with <code>setMyState(0)</code>, so that when a new image is loaded it will trigger again the <code>useLayoutEffect</code></li>
  <li><strong>BUT</strong>, when I reset the state it repaints the images, so the images get loaded again and trigger again the <code>useLayoutEffect</code>, which again resets the state, creating an infinite loop</li>
  <li>If I don't reset the state, the loop is stopped</li>
  <li><strong>BUT</strong> then when a new image is loaded with the scroll, <code>setMyState(1)</code> doesn't change anything and the <code>useLayoutEffect</code> is not triggered</li>
  </ul>
  
  <hr>
  
  <h2>So I'm now stuck</h2>
  
  <p>This is a simplified code</p>
  
  <pre class="lang-js prettyprint-override"><code>import React, { useRef, useLayoutEffect, useState} from "react";
  
  const wait = ms =&gt;
    new Promise((res, rej) =&gt; setTimeout(() =&gt; res("timed"), ms));
  
  const Image = ({ setImageLoader }) =&gt; {
    // lazy load the image with an Observer, and then changes the state
    setImageLoader(1);
    return &lt;&gt;&lt;/&gt;;
  };
  
  export default function App() {
    const [imageLoaded, setImageLoader] = useState(0);
  
    useLayoutEffect(() =&gt; {
      const update_grid = async stillMounted =&gt; {
        // change stuff
        await wait(10000); // careful with crashing chrome
        console.log("updated");
        setImageLoader(0);
      };
  
      const stillMounted = { value: true };
      update_grid(stillMounted);
      return () =&gt; (stillMounted.value = false);
    }, [imageLoaded]);
  
    return &lt;Image setImageLoader={setImageLoader} /&gt;;
  }
  </code></pre>
  
  <hr>
  
  <h1>Attempt 1</h1>
  
  <p>I cannot change the state in the <code>useLayoutEffect</code>, so I will change stuff only when the images are loaded and not again</p>
  
  <h1>Problem</h1>
  
  <p>When I scroll and more images are loaded, I cannot triger the useLayoutEffect anymore</p>
  
  <pre class="lang-js prettyprint-override"><code>import React, { useRef, useLayoutEffect, useState} from "react";
  
  const wait = ms =&gt;
    new Promise((res, rej) =&gt; setTimeout(() =&gt; res("timed"), ms));
  
  const Image = ({ setImageLoader }) =&gt; {
    setImageLoader(1);
    return &lt;&gt;hey&lt;/&gt;;
  };
  
  export default function App() {
    const [imageLoaded, setImageLoader] = useState(0);
  
    useLayoutEffect(() =&gt; {
      // change stuff
      const update_grid = async stillMounted =&gt; {
        await wait(40000);
        console.log("updated");
        // setImageLoader(0); NOW THIS IS NOT CAUSING THE REPAINT, BUT NEITHER WHEN NEW IMAGES ARE LOADED
      };
  
      const stillMounted = { value: true };
      update_grid(stillMounted);
      return () =&gt; (stillMounted.value = false);
    }, [imageLoaded]);
  
    return &lt;Image setImageLoader={setImageLoader} /&gt;;
  }
  </code></pre>
  
  <hr>
  
  <h1>Attempt 2</h1>
  
  <p>If I don't want the repaint, but want to trigger the useLayoutEffect, I could use <code>useRef</code> instead of <code>useState</code></p>
  
  <pre class="lang-js prettyprint-override"><code>import React, { useRef, useLayoutEffect, useState, useEffect } from "react";
  
  const wait = ms =&gt;
    new Promise((res, rej) =&gt; setTimeout(() =&gt; res("timed"), ms));
  
  const Image = ({ imageLoaded }) =&gt; {
    imageLoaded.current++;
    return &lt;&gt;hey&lt;/&gt;;
  };
  
  export default function App() {
    const imageLoaded = useRef(0);
  
    useLayoutEffect(() =&gt; {
      // change stuff
      const update_grid = async stillMounted =&gt; {
        await wait(10000);
        console.log("updated " + imageLoaded.current);
        imageLoaded.current++;
      };
  
      const stillMounted = { value: true };
      update_grid(stillMounted);
      return () =&gt; (stillMounted.value = false);
    }, [imageLoaded.current]);
  
    return &lt;Image imageLoaded={imageLoaded} /&gt;;
  }
  </code></pre>
  
  <p>But codesandbox already warns me that <code>imageLoaded.current</code> will not cause a re-render of the component (even though I more or less wants this?)</p>
  
  <p>So at the end, I see <code>imageLoaded.current</code> increasing when new images are loaded, but the <code>useLayoutEffect</code> is not triggered</p>