Ankit Jaishwal
Ankit Jaishwal

Reputation: 526

How to call a function in useEffect only after getting data from backend?

What I have to achieve is to show the data obtained as response from api in sorted order from first render itself.

This is how I'm using useEffect hooks:-

useEffect(()=>{
        async function fetchProject() {
            await axios.post(envurls.wv+'/show_project',{
                params:{
                    authtoken:authtoken,
                    user:user
                }
            }).then((response)=>{
                console.log('Res', response.data);
                setShowProject(response.data)
                
            }).catch((error)=>{
                console.log(error)
            })
        }
        fetchProject();
        
    },[]);

    useEffect(() => {
        showProject.length> 0 && sortArray();
    }, []); 

   
    const sortArray = ()=> {
        const sortProperty = 'timestamp';
     
        sorted = [...showProject].sort((a, b) => (a[sortProperty] > b[sortProperty] ? -1 : 1))
        console.log("Project", showProject);
        console.log("Sorted Data", sorted);
        setShowProject(sorted);
    };

But on first render, it is not sorting data as showProject array is empty. So, I'm getting Project and Sorted Data as empty array on console.

And If I provide showProject in useEffect like this:-

useEffect(() => {
    showProject.length> 0 && sortArray();
}, [showProject]); 

Then it is displaying sorted data for the first render itself but Project and Sorted Data are geetind displayed for n number of times in console.

Upvotes: 0

Views: 1009

Answers (2)

Michael Hoobler
Michael Hoobler

Reputation: 622

As far as I'm aware, the only way to get your "sorted data" on the first render is by passing the sorted data into the component through props. You are also needlessly updating your showProject twice which will cause unnecessary rerenders and @Asaf Aviv's answer is a good work around to that.

The best way to call a function only after getting data from the backend is to put the function call inside of the .then() callback or inside of and async function after an await. Right now your sortArray() is NOT doing either of those, it's outside the scope of your async function and await has no effect on it. The component renders, checks the showProject.length> 0 condition, and if the condition is met it runs the function.

Also, you don't need to use .then() or .catch() inside of an async function, this is a more typical way to do it:

async function fetchProject() {
  try {
    let response = await axios.post(envurls.wv + "/show_project", {
      params: {
        authtoken: authtoken,
        user: user
      }
    });
    // everything past here runs ONLY AFTER getting a response back from axios.post
    setShowProject(response.data)
  } catch (err) {
    console.log(err);
  }
}
   

Upvotes: 0

Asaf Aviv
Asaf Aviv

Reputation: 11760

You can use useMemo and set the dependencies to the data in state and the sorting parameters.

useMemo will call that function when one of the dependencies has changed exactly like useEffect, but this time it will return the value we return from that function.

This way we don't touch the original state and just sort it after changes

useEffect(() => {
  async function fetchProject() {
    const { data } = await axios.post(envurls.wv + "/show_project", {
      params: {
        authtoken: authtoken,
        user: user
      }
    });

    setShowProject(data);
  }

  fetchProject().catch(e => /* handle error here */);
}, []);

const sorted = useMemo(() => {
  const sortProperty = "timestamp";

  // use the sorting parameters here

  return [...showProject].sort((a, b) =>
    a[sortProperty] > b[sortProperty] ? -1 : 1
  );
}, [showProject, sortType]);

console.log(sorted);

Upvotes: 1

Related Questions