When I try to setState in useEffect it doesnt work

Viewed 55

After getting the documents I want and storing as objects in an array shown here

const appealsRef = collection(db, "appeals");
const today = new Date();
const getAppeals = async (logicalOp) => {
        const q = query(appealsRef, where("toDate", logicalOp, convertDateToTimestamp(today)))
        const querySnapshot = await getDocs(q);
        let dummyAppeals = []; 
        querySnapshot.docs.map((doc) => {
            const docData = { ...doc.data() };
            getOrgByDocID(docData.orgDocID)
                .then((res) => {
                    const appealData = {
                        ...docData,
                        orgName : res.orgName, 
                        orgAddress : res.orgAddress
                    }
                    dummyAppeals.push(appealData)
                    
                })
        });
        return dummyAppeals
    }

I would like to set the results in state variables currentAppeals and pastAppeals based on the logicalOp given in useEffect and then set it into another state variable array appeals using setAppeals; this is because depending on what the user clicks on which is past or present I would like to set the appeals array to currentAppeals if present is clicked or pastAppeals if past is clicked.

The code involved :

const [ currentAppeals, setCurrentAppeals ] = useState([]);
const [ appeals, setAppeals ] = useState([]);
useEffect(() => {
        getAppeals(">=")
            .then((res) => {
                setCurrentAppeals(res);
                setAppeals(currentAppeals.map((appeal, i) => {
                    return (
                        <AppealCard
                            appealID={appeal.appealID}
                            from={appeal.fromDate}
                            to={appeal.toDate}
                            orgName={appeal.orgName}
                            orgAddress={appeal.orgAddress}
                            outcome={appeal.outcome}/>
                    )
                }));
            });
    }, []);

And then I will display the appeals in the react component here :

<div className='view-appealsBody'>
        {
            appeals
        }
</div>

But the appeals are not set after setAppeals in useEffect().

4 Answers

Issues

  1. getAppeals doesn't wait for the dummyAppeals array to become populated, it simply returns the empty array while the asynchronous code in the map callback runs.
  2. React state updates are processed asynchronously, so currentAppeals is still the initial empty array value when the code tries to map it to JSX.
  3. It's anti-pattern in React to store JSX in state. You should instead store the data and map the state to JSX when rendering. This avoid any stale enclosures over any previous state values.

Solution

Update getAppeals to return the array of resolved promises containing the appeals

const appealsRef = collection(db, "appeals");
const today = new Date();

const getAppeals = async (logicalOp) => {
  const q = query(appealsRef, where("toDate", logicalOp, convertDateToTimestamp(today)));
  const querySnapshot = await getDocs(q);

  return Promise.all(querySnapshot.docs.map((doc) => {
    const docData = { ...doc.data() };
    return getOrgByDocID(docData.orgDocID)
      .then((res) => ({
        ...docData,
        orgName : res.orgName, 
        orgAddress : res.orgAddress
      }))
    })
  );
};

...

const [currentAppeals, setCurrentAppeals] = useState([]);

useEffect(() => {
  getAppeals(">=").then((res) => setCurrentAppeals(res));
}, []);

return (
  ...
  {currentAppeals.map((appeal, i) => (
    <AppealCard
      key={appeal.appealID}
      appealID={appeal.appealID}
      from={appeal.fromDate}
      to={appeal.toDate}
      orgName={appeal.orgName}
      orgAddress={appeal.orgAddress}
      outcome={appeal.outcome}/>
    ))}
  ...
);

Check this out

// const [ currentAppeals, setCurrentAppeals ] = useState([]);
const [ appeals, setAppeals ] = useState([]);
useEffect(() => {
        getAppeals(">=")
            .then((res) => {
              // setCurrentAppeals(res);
                setAppeals(res.map((appeal, i) => {
                    return (
                        <AppealCard
                            appealID={appeal.appealID}
                            from={appeal.fromDate}
                            to={appeal.toDate}
                            orgName={appeal.orgName}
                            orgAddress={appeal.orgAddress}
                            outcome={appeal.outcome}/>
                    )
                }));
            });
    }, []);

you must set appeals as second parameter in useEffect

useEffect(() => {
        getAppeals(">=")
            .then((res) => {
                setCurrentAppeals(res);
                
            });
    }, [appeals]);

and better put this code to return the body of component

currentAppeals.map((appeal, i) => {
                    return (
                        <AppealCard
                            appealID={appeal.appealID}
                            from={appeal.fromDate}
                            to={appeal.toDate}
                            orgName={appeal.orgName}
                            orgAddress={appeal.orgAddress}
                            outcome={appeal.outcome}/>
                    )
                });

It happens because React setState is an asynchronous function. You're setting appeals using previously set state i.e currentAppeals. Instead of try to set it using res only like:

const [ currentAppeals, setCurrentAppeals ] = useState([]);
const [ appeals, setAppeals ] = useState([]);
useEffect(() => {
        getAppeals(">=")
            .then((res) => {
                setCurrentAppeals(res);
                setAppeals(res.map((appeal) => {
                    return (
                        <AppealCard
                            key={appeal.appealID}
                            appealID={appeal.appealID}
                            from={appeal.fromDate}
                            to={appeal.toDate}
                            orgName={appeal.orgName}
                            orgAddress={appeal.orgAddress}
                            outcome={appeal.outcome}/>
                    )
                }));
            });
    }, []);
Related