When I click on the search button I send a POST request and get data from the server. The thing is the first time it returns an empty array based on my console log report, but when I click on the button for the second time it has the values from the server and my UI changes. Why does this happen? is it because async-await process? what should I do to solve this problem?
export default function Home() {
const [searchTerm, setSearchTerm] = useState('');
const obj = { searchTerm };
const [receivedData, setReceivedData] = useState([]);
const [episList, setEpisList] = useState([]);
const [isPlaying, setIsPlaying] = useState(true);
const [currentUrlIndex, setCurrentUrlIndex] = useState(0);
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(obj),
};
const submitSearch = async () => {
const response = await fetch('http://localhost:3001/', options);
const data = await response.json();
//COMMENT--- Manipulating and using the result I get from server
setReceivedData(data.body.result);
const epName = receivedData.map((x) => {
return x.name;
});
setEpisList(epName);
console.log(epName);
};
return (
<>
<main>
<div>
<ReactPlayer
controls={true}
url={`videos/${episList[currentUrlIndex]}.mkv`}
/>
</div>
)}
</div>
</div>
<div>
<SearchBox
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<SearchButton onClick={submitSearch} />
</div>
</main>
</>
);
}
CodePudding user response:
Your issue lies in the following code:
//COMMENT--- Manipulating and using the result I get from server setReceivedData(data.body.result); const epName = receivedData.map((x) => { return x.name; });
setReceivedData(data.body.result)
will update receivedData
on the next render. The current execution context still uses the old (stale) value. Meaning that epName
will lack behind, because it is based on the old receivedData
.
So how do you solve this? Quite simple, store the new value in a variable.
// create a shadow variable that holds the new value
const receivedData = data.body.result;
setReceivedData(receivedData);
const epName = receivedData.map((x) => {
return x.name;
});
The above creates a new variable receivedData
and assigns it the new value. This does not update the state, so we still need to call setReceivedData(receivedData)
which update the component state. After that we use this variable instead of the state.
If you don't quite understand shadow variables, imagine the above code like this:
// create a variable that holds the new value
const newReceivedData = data.body.result;
setReceivedData(newReceivedData);
const epName = newReceivedData.map((x) => {
return x.name;
});
A shadow variable does the exact same, but uses a name that is already defined in an outer scope.
CodePudding user response:
So, when you set a react state, you schedule a rerender. When that happens, the body of your function(-al component) is going to run again, recreating all variables, including submitSearch
where you store your function.
submitSearch
closes over several references to variables who's "content" changes every time that function is redefined (every render), and makes several calls to set state. I think stuff like this is best avoided, since it can be a little bit annoying to debug.
Consider letting submitSearch
have a single responsibility: to retrieve backend data to a state.
const submitSearch = async () => {
const response = await fetch('http://localhost:3001/', options);
const data = await response.json();
setReceivedData(data.body.result);
}
Then you can generate side effects on that state
React.useEffect(() => {
setEpisList(receivedData?.map((x) => x.name)
},[receivedData, setEpisList])
But, episList is really just a nice-to-have subset of receivedData, you could redefine it per render, without having a state for it.
const episList = receivedData?.map((x) => x.name)
If you want to take it a step further, consider relocating things out of the component, and consider errors
const myLocalhostApi = {
get: (options) => fetch('http://localhost:3001/', options).then(r => r.json()
}
const MyComponent() {
const [receivedData, setReceivedData] = React.useState([])
const options = // options
const handleSubmit = myLocalhostApi.get(options)
.then(setReceivedData)
.catch(myErrorHandler)
const episList = receivedData.map((x) => x.name)
...
CodePudding user response:
Essentially you are trying to update in synchronous time a variable with asynchronous data. To solve you problem you have to use then
after the fetch
statement.
You have to rewrite your submitSearch
function like that:
const submitSearch = () => {
fetch('http://localhost:3001/', options)
.then(response => response.json())
.then(result => {
setReceivedData(result);
const epName = receivedData.map((x) => {
return x.name;
});
setEpisList(epName);
console.log(epName);
})
};
In this way after the fetch
, if everything is ok, you enter in the then
and there you update your state.