You are correct in pointing out that there could be a problem with your loadCountryData1 function, because it could potentially use an outdated value of selected if selected is updated between the time the function is called and the time the API request is made.
In your loadCountryData2 function, you are calling setSelected, and then making the API call, but this does not actually solve the problem because setSelected is asynchronous and does not guarantee that the new value will be used in the API call.
Using the React useEffect hook would watch the selected state and then load the data whenever it changes. This will ensure that you always have the correct, up-to-date value when making the API call:
import { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState(null);
const [countryData, setCountryData] = useState(null);
const handleSelect = (e) => {
setSelected(e.currentTarget.value);
};
useEffect(() => {
if (selected !== null) {
axios
.get(`https://restcountries.com/v3.1/name/${selected}`)
.then((response) => setCountryData(response.data));
}
}, [selected]); // re-run this effect whenever `selected` changes
return (
<div className="App">
<div className="container">
<select value={selected} onChange={handleSelect}>
<option value={null}>Select country</option>
<option value="india">India</option>
<option value="usa">USA</option>
<option value="germany">Germany</option>
</select>
</div>
<div>
{countryData !== null && (
<pre>{JSON.stringify(countryData, null, 4)}</pre>
)}
</div>
</div>
);
}
The useEffect hook would be set up here to run any time selected changes. This means that whenever a new country is selected, the effect will run and fetch the new data. Note that the axios call is inside the if (selected !== null) block to ensure that no API call is made when selected is null.
But that means there is no longer a need for a separate loadCountryData function because the data loading is now automatically tied to the selected state. Which is not what you want.
What if I want it to trigger onClick like in the example?
In this case, you would need to create another piece of state to trigger the API call. This state would be updated within the onClick handler.
import { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState(null);
const [countryData, setCountryData] = useState(null);
const [shouldFetch, setShouldFetch] = useState(false);
const handleSelect = (e) => {
setSelected(e.currentTarget.value);
};
useEffect(() => {
if (selected !== null && shouldFetch) {
axios
.get(`https://restcountries.com/v3.1/name/${selected}`)
.then((response) => {
setCountryData(response.data);
setShouldFetch(false); // reset the fetch trigger
});
}
}, [selected, shouldFetch]); // re-run this effect whenever `selected` or `shouldFetch` changes
const loadCountryData = () => {
setShouldFetch(true);
};
return (
<div className="App">
<div className="container">
<select value={selected} onChange={handleSelect}>
<option value={null}>Select country</option>
<option value="india">India</option>
<option value="usa">USA</option>
<option value="germany">Germany</option>
</select>
<button onClick={loadCountryData} disabled={selected === null}>
Load
</button>
</div>
<div>
{countryData !== null && (
<pre>{JSON.stringify(countryData, null, 4)}</pre>
)}
</div>
</div>
);
}
In this modified version of the code, I have added a shouldFetch piece of state that serves as a trigger for the API call. This trigger is set to true when the "Load" button is clicked, which causes the useEffect hook to run.
After the API call is made, shouldFetch is reset to false to prevent further API calls until the button is clicked again.
This approach gives you control over when the API call is made, while still ensuring that the most recent value of selected is used.