Kevin Chin Jia Wen
Kevin Chin Jia Wen

Reputation: 23

When I try to setState in useEffect it doesnt work

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().

Upvotes: 2

Views: 66

Answers (4)

Drew Reese
Drew Reese

Reputation: 202618

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}/>
    ))}
  ...
);

Upvotes: 3

Harsh Patel
Harsh Patel

Reputation: 6830

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}/>
                    )
                }));
            });
    }, []);

Upvotes: 0

Arash Ghazi
Arash Ghazi

Reputation: 991

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}/>
                    )
                });

Upvotes: 0

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}/>
                    )
                }));
            });
    }, []);

Upvotes: 0

Related Questions