5

I have a dropdown which when changes calls api to fetch data using useEffect hook:

const [selectedComplianceFilter, setComplianceFilter] = useState("all");

useEffect(() => {
  if (selectedComplianceFilter === "all") {
    fetchFullReport();
  } else {
    fetchReportByCompliance(selectedComplianceFilter);
  }
}, [selectedComplianceFilter]);

<select
  name="compliance"
  className="custom-select w-auto"
  onChange={e => setComplianceFilter(e.target.value)}
>
  <option value="all">All</option>
  {compliances.map((compliance, i) => (
    <option key={i} value={compliance}>
      {compliance}
    </option>
  ))}
</select>

I have another useEffect hook which calls another api which returns scan status

useEffect(() => {
  fetchCompliances();

  let intervalId;

  getScanStatus(0);
  fetchFullReport();

  intervalId = setInterval(() => {
    getScanStatus(intervalId);
  }, 10000);

  return () => {
    componentDidUnmount.current = true;
    clearInterval(intervalId);
  };
}, []);

This is the function to fetch scan status:

const [scanStatus, setScanStatus] = useState("");
const [lastUpdated, setLastUpdated] = useState();
const [reports, setReports] = useState([]);

const getScanStatus = async intervalId => {
  const resp = await API.get(SCAN_STATUS_URL);

  if (resp && resp.status) {
    const { scanStatus = "", lastModifiedDate = "" } = resp.data || {};

    const isValidDate = moment(lastModifiedDate).isValid();

    if (!componentDidUnmount.current) {
      setScanStatus(scanStatus);
      isValidDate && setLastUpdated(getFormattedDateTime(lastModifiedDate));
      if (scanStatus === "COMPLETED") {
        clearInterval(intervalId);

        console.log("selectedComplianceFilter", selectedComplianceFilter); // <== returns old state i.e., 'all' but should be latest selected option.

        if (selectedComplianceFilter === "all") {
          fetchFullReport();
        } else {
          fetchReportByCompliance();
        }
      }
    }
  }
};

Now what happens is when page loads fetchFullReport(); renders the table. Now when the dropdown is changed fetchReportByCompliance(); is called to filter the table and table gets updated with fewer rows now.

Meanwhile getScanStatus is called on every 10 second interval and as soon as the api returns status = 'COMPLETED' and the interval is stopped and the table gets updated with new data (fetchFullReport();) and all the table rows are back which is a weird behavior as dropdown filter still has selected option other than ALL option. So, when new data arrives as soon as scan is completed to update the table I added an if condition to update table based on selectedComplianceFilter value.

But when the getScanStatus function api returns status = 'COMPLETED' the value of selectedComplianceFilter is still 'all' but the dropdown has another option selected. I am s assuming it is because of async await block which still holds the previous value of selectedComplianceFilter state.

How do I get the latest value of selectedComplianceFilter state when getScanStatus executes in the future as it is asynchronous?

Sushmit Sagar
  • 1,412
  • 2
  • 13
  • 27

1 Answers1

1

What about wrapping your code in a useEffect block which fires when scanStatus changes? For example:

useEffect(() => {
    if (scanStatus === "COMPLETED") {
        if (selectedComplianceFilter === "all") {
            fetchFullReport();
        } else {
            fetchReportByCompliance();
        }
   }
}, [scanStatus]);
Ed Lucas
  • 5,955
  • 4
  • 30
  • 42
  • Thanks for the answer but I have solved it now. After some research I found out that its the issue of closure with `setInterval` within `useEffect` hook from [here](https://stackoverflow.com/q/53024496/6885729). Now I am using @DanAbramov's `useInterval` [custom hook](https://overreacted.io/making-setinterval-declarative-with-react-hooks/). However, your solution does look simple and good enough. – Sushmit Sagar Feb 20 '20 at 17:51
  • That looks like a very useful custom hook. – Ed Lucas Feb 20 '20 at 18:08
  • Yes. But your solution is very easy and I was like whaaat? why didn't I think of that? it's so simple. Now I am confused about which one is better solution and which one to keep yours or useInterval custom hook one? hehe – Sushmit Sagar Feb 20 '20 at 18:12
  • Gotcha. I'm not used to thinking in terms of custom hooks yet, but I'm doing a lot with just useState and useEffect. – Ed Lucas Feb 20 '20 at 18:50