Search code examples
javascriptreactjsuse-effectcode-splittingdynamic-import

React: ES2020 dynamic import in useEffect blocks rendering


From what I understand, async code inside useEffect runs without blocking the rendering process. So if I have a component like this:

const App = () => {
  useEffect(() => {
    const log = () => console.log("window loaded");
    window.addEventListener("load", log);
    return () => {
      window.removeEventListener("load", log);
    };
  }, []);

  useEffect(() => {
    const getData = async () => {
      console.log("begin");
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/todos/1"
      );
      const data = await response.json();
      console.log("end");
    };
    getData();
  }, []);

  return null;
};

The console output is (in order):

begin
window loaded
end

However if I use ES2020 dynamic import inside the useEffect:

const App = () => {
  useEffect(() => {
    const log = () => console.log("window loaded");
    window.addEventListener("load", log);
    return () => {
      window.removeEventListener("load", log);
    };
  }, []);

  useEffect(() => {
    const getData = async () => {
      console.log("begin");
      const data = await import("./data.json");
      console.log("end");
    };
    getData();
  }, []);

  return null;
};

The output is (in order):

begin
end
window loaded

This is a problem because when data.json is very large, the browser hangs while importing the large file before React renders anything.


Solution

  • All the necessary and useful information is in Terry's comments. here is the implementation of what you want according to the comments:

    First goal:

    I would like to import the data after window has loaded for SEO reasons.

    Second goal:

    In my case the file I'm trying to dynamically import is actually a function that requires a large dataset. And I want to run this function whenever some state has changed so that's why I put it in a useEffect hook.

    Let's do it

    You can create a function to get the data as you created it as getData with useCallback hook to use it anywhere without issue.

    import React, {useEffect, useState, useCallback} from 'react';
    
    function App() {
      const [data, setData] = useState({});
      const [counter, setCounter] = useState(0);
    
      const getData = useCallback(async () => {
        try {
          const result = await import('./data.json');
          setData(result);
        } catch (error) {
          // do a proper action for failure case
          console.log(error);
        }
      }, []);
    
      useEffect(() => {
        window.addEventListener('load', () => {
          getData().then(() => console.log('data loaded successfully'));
        });
    
        return () => {
          window.removeEventListener('load', () => {
            console.log('page unmounted');
          });
        };
      }, [getData]);
    
      useEffect(() => {
        if (counter) {
          getData().then(() => console.log('re load data after the counter state get change'));
        }
      }, [getData, counter]);
    
      return (
        <div>
          <button onClick={() => setCounter((prevState) => prevState + 1)}>Increase Counter</button>
        </div>
      );
    }
    
    export default App;
    

    Explanation:

    With component did mount, the event listener will load the data.json on 'load' event. So far, the first goal has been met.

    I use a sample and simple counter to demonstrate change in the state and reload data.json scenario. Now, with every change on counter value, the getData function will call because the second useEffect has the counter in its array of dependencies. Now the second goal has been met.

    Note: The if block in the second useEffect prevent the getData calling after the component did mount and first invoking of useEffect

    Note: Don't forget to use the catch block for failure cases.

    Note: I set the result of getData to the data state, you might do a different approach for this result, but the other logics are the same.