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 untilawait 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 |