Home > Net >  Setting a loader before component loads
Setting a loader before component loads

Time:01-01

I am trying to only show the CircularProgress loader when the products have not been loaded yet. Without loading, the code shows the empty state from else. I need to add the loader so that the CircularProgress shows before products loads in instead of the empty state. Currently receiving a runtime error at const data = await products();

  const [loading, setLoading] = useState(false);

  useEffect(() => {
    (async () => {
      const data = await products();
      if (data) {
        setLoading(false);
      }
      setLoading(true);
    })();
  }, []);

  {
    loading && <CircularProgress/>;
  }
  if (props.productList && props.productList.length > 0) {
    products = props.productList.map((item, index) => {
      return (
        item.status === 1 && (
          <Col key={index}>
            <Product
              {...item}/>
          </Col>
        )
      );
    });
  } else {
    products = (
      {...empty state}
    );
  }

CodePudding user response:

You can await an async function call (or one that returns a Promise), not a variable (if its value is not a Promise).

In your case, you could just display your <CircularProgress/> in your else block:

  if (props.productList && props.productList.length > 0) {
    return props.productList.map((item, index) => {
      return (
        item.status === 1 && (
          <Col key={index}>
            <Product
              {...item}/>
          </Col>
        )
      );
    });
  } else {
    return (
      <React.Fragment>
        <CircularProgress/>;
        {/* empty display... */}
      </React.Fragment>
    )
  }

Note that you do not seem to have a criterion to differentiate between loading and not loading but empty states, hence here I put both loading and empty displays together.

If you want to separate them, you could use a 3rd type for your productList prop, which would let you add a 3rd else if block; typically:

  • null for loading (initial state)
  • [] for no longer loading, but empty
  • [ ... ] (length > 0) no longer loading and not empty
// In parent component that fetches data
const [productList, setProductList] = useState(null); // Initial state null before data is loaded

useEffect(() => {
  fetchData() // Your data retrieving function
    .then((data) => setProductList(data)); // Assuming data is ALWAYS an array, possibly empty
}, []);

// In component that displays the list
if (props.productList === null) {
  // Data is still loading
  return <CircularProgress/>;

} else if (props.productList.length === 0) {
  // Data is loaded but empty
  return {/* empty display... */};

} else {
  // Data is loaded and not empty
  return <React.Fragment>
    { productList.map((item, index) => {/* etc. */}) }
  </React.Fragment>;
}

Depending on how you actually fetch your productList data, you may also have an explicit loading flag, that you could use as a separate prop.

CodePudding user response:

You want to set your initial loading state to 'true':

// Change:
const [loading, setLoading] = useState(false);

// To:
const [loading, setLoading] = useState(false);

The problem is that you set it initially to false, so on first load it will not show the <CircularProgress /> component.

Your effect:

 useEffect(() => {
    (async () => {
      const data = await products();
      if (data) {
        setLoading(false);
      }
      setLoading(true);
    })();
  }, []);

is an async IIFE (Immediately Invoked Function Expression), with no dependencies meaning:

  • It is called on initial render only (without updating when props change)
  • Any code after the await will not execute until await products(); has resolved.

This means that essentially loading is false (the initial value set in useState(false)) until the products(); function resolves or rejects. If it is generally going to contain data, this means that the state will not show any loading, at all. The only time it will show loading is if your products(); call fails (does not contain data) in which case the setLoading(false) call will set it to false and the <CircularProgress/>; component will display.

To go over your current code (assuming it is the content of a functional component):

// Functional component definition
const MyComponent = () => {
  // State store, initially set to 'false', meaning that until changed, the value of `loading` === false
  const [loading, setLoading] = useState(false);

  // Side effect function - runs once on load, and then never again 
  (as dependency array is empty)
  useEffect(() => {
    // Define an async IIFE. This code will immediately execute when 
    the component loads, and then never again.
    (async () => {
      // await here will wait for the 'products' function to 
      'resolve' or 'reject' before continuing on to the next line...
      const data = await products();
      // By this point, 'loading' has been stuck on 'false' until 
      'products' has resolved/rejected.
      if (data) {
        // Data is returned, we are no longer 'loading'
        setLoading(false);
      }
      // Data is not returned and we should set `loading` to `true`
      setLoading(true);
    })();
  }, []);

  // There should be a `return` here e.g: `return loading ?
  // '{COMPONENT_TO_SHOW_WHEN_LOADING}' : 
  // '{COMPONENT_TO_SHOW_WHEN_LOAD_COMPLETE}'
  // Otherwise this code is fine, providing that `loading` is set correctly while awaiting the `data` from the `products()` function.
  {
    loading && <CircularProgress/>;
  }
  if (props.productList && props.productList.length > 0) {
    products = props.productList.map((item, index) => {
      return (
        item.status === 1 && (
          <Col key={index}>
            <Product
              {...item}/>
          </Col>
        )
      );
    });
  } else {
    products = (
      {...empty state}
    );
  }
}

CodePudding user response:

The main problem you are facing is the architecture problem. You are awaiting for products to load and changing the loading states but you are not using the data anywhere in the code !!!

Your approach

State:
loading => true
   After data = products() => loading false
Component:
if loading => show Loader

if props               |
       show ...        |
else                   |             
  • Related