2

I am creating a React.js app which got 2 components - The main one is a container for the 2nd and is responsible for retrieving the information from a web api and then pass it to the child component which is responsible for displaying the info in a list of items. The displaying component is supposed to present a loading spinner while waiting for the data items from the parent component.

The problem is that when the app is loaded, I first get an empty list of items and then all of a sudden all the info is loaded to the list, without the spinner ever showing. I get a filter first in one of the useEffects and based on that info, I am bringing the items themselves.

The parent is doing something like this:

useEffect(() =>
{
    async function getNames()
    {
        setIsLoading(true);
        const names = await WebAPI.getNames();
        setAllNames(names);
        setSelectedName(names[0]);
        setIsLoading(false);
    };
    getNames();
  } ,[]);

  useEffect(() =>
  {
    async function getItems()
    { 
        setIsLoading(true);
        const items= await WebAPI.getItems(selectedName);
        setAllItems(items);
        setIsLoading(false);
    };
    getTenants();
  },[selectedName]);

  .
  .
  .
  return (
     <DisplayItems items={allItems} isLoading={isLoading} />
   );

And the child components is doing something like this:

let spinner = props.isLoading ? <Spinner className="SpinnerStyle" /> : null; //please assume no issues in Spinner component
let items = props.items;
return (
   {spinner}
   {items}
)

I'm guessing that the problem is that the setEffect is asynchronous which is why the component is first loaded with isLoading false and then the entire action of setEffect is invoked before actually changing the state? Since I do assume here that I first set the isLoading and then there's a rerender and then we continue to the rest of the code on useEffect. I'm not sure how to do it correctly

3 Answers 3

1

The problem was with the asynchronicity when using mulitple useEffect. What I did to solve the issue was adding another spinner for the filters values I mentioned, and then the useEffect responsible for retrieving the values set is loading for that specific spinner, while the other for retrieving the items themselves set the isLoading for the main spinner of the items.

Sign up to request clarification or add additional context in comments.

1 Comment

Could you give an example?
0

instead of doing it like you are I would slightly tweak it:

remove setIsLoading(true); from below

useEffect(() =>
{
    async function getNames()
    {
        setIsLoading(true); //REMOVE THIS LINE
        const names = await WebAPI.getNames();
        setAllNames(names);
        setSelectedName(names[0]);
        setIsLoading(false);
    };
    getNames();
  } ,[]);

and have isLoading set to true in your initial state. that way, it's always going to show loading until you explicitly tell it not to. i.e. when you have got your data

also change the rendering to this:

let items = props.items;
return isLoading ? (
   <Spinner className="SpinnerStyle" />
) : <div> {items} </div>

8 Comments

It's actually already true in the initial state and I just set it again to true at the beginning of every action
but if the spinner is true, how are you not seeing the spinner? are you sure this is true? props.isLoading also I've changed how you should show the spinner in my code
@YonatanNir try rendering like this instead
I tried now and when I'm debugging I can see the spinner until the first useEffect ends with setIsLoading(false), but it seems like other useEffect already passed the point of setIsLoading(true) so no one else is turning it back on
did you try the different rendering? did it have effect?
|
0

this is full example with loading :

const fakeApi = (name) =>
  new Promise((resolve)=> {
    setTimeout(() => {
      resolve([{ name: "Mike", id: 1 }, { name: "James", id: 2 }].filter(item=>item.name===name));
    }, 3000);
  })
  
  const getName =()=>   new Promise((resolve)=> {
    setTimeout(() => {
      resolve("Mike");
    }, 3000);
  })
const Parent = () => {
  const [name, setName] = React.useState();
   const [data, setData] = React.useState();
  const [loading, setLoading] = React.useState(false);
    const fetchData =(name) =>{
    if(!loading) setLoading(true);
      fakeApi(name).then(res=>
      setData(res)
      )
      }
    const fetchName = ()=>{
    setLoading(true);
    getName().then(res=> setName(res))
    }

  React.useEffect(() => {
    fetchName();
  }, []);
  React.useEffect(() => {
   if(name)fetchData(name);
  }, [name]);
    React.useEffect(() => {
  if(data && loading) setLoading(false)
  }, [data]);
  return (
    <div>
      {loading
        ? "Loading..."
        : data && data.map((d)=>(<Child key={d.id} {...d} />))}
    </div>
  );
};
const Child = ({ name,id }) =>(<div>{name}  {id}</div>)

ReactDOM.render(<Parent/>,document.getElementById("root"))
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<div id="root"></div>

2 Comments

The issue is with the asynchronicity when using mulitple useEffect. All the setLoading(true) are called first and after that all the setLoading(false), while what I want is them to be paired true false
so what are you trying to do is to have same loading for both async action ??

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.