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.