React - API call running multiple times - reactjs

I'm writing a react app that fetches data from two different APIs. In the code below, I call the two APIs then console.log the response. After npm start, in the console, I see that the response has been console.logged a few times. I don't know why it's doing this and it's causing issue with the app's behavior. It's receiving the data from aws dynamoDB tables.
function App() {
const [ response , setResponse ] = useState();
const [ btnText , setbtnText ] = useState('Get Data');
const [ details , setDetails ] = useState();
async function fetchData() {
try {
await fetch('url hidden' , {
method: 'POST',
header: {'Content-Type': 'application/json'},
}).then(res => res.json())
.then(res => setResponse(res))
await fetch('url hidden' , {
method: 'POST',
header: {'Content-Type': 'application/json'},
}).then(res => res.json())
.then(res => setDetails(res))
} catch (error) {
console.log(error);
};
}
console.log(response)
return (
<div className="container">
<header className='header'>
<button onClick={fetchData}>{btnText}</button>
</header>
<Summary response={response} details={details} />
</div>
);
}
I also tried useEffect to fetch data as soon as app loads, but it's doing the same thing.
useEffect(() => {
try {
Promise.all([
fetch('url hidden' , {
method: 'POST',
header: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(res => setResponse(res)),
fetch('url hidden' , {
method: 'POST',
header: {'Content-Type': 'application/json'},
}).then(res => res.json()).then(res => setDetails(res)),
]);
}
catch(err) {
console.log(err);
}
} , [])
this image shows the response after clicking the button only once

When you use console.log in the the functional component you will get that console.log each time the component rerenders. And that happens for example each time you set a new state.
You could just move the console.log inside the fetch function.
Or you just console.log the values in useEffect if they change. Like in the example below.
I also refactored the fetchData function to use async await and Promise.all more efficient.
Also you were missing an "s" for "headers" for the fetch method.
async function fetchData() {
try {
const [response, details] = await Promise.all([
(
await fetch("url hidden", {
method: "POST",
headers: { "Content-Type": "application/json" },
})
).json(),
(
await fetch("url hidden", {
method: "POST",
headers: { "Content-Type": "application/json" },
})
).json(),
]);
setResponse(response);
setDetails(details);
} catch (error) {
console.log(error);
}
}
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
console.log(response, details);
}, [response, details]);

If you move console.log(response) inside fetchData then you will get exact information about how many times handler executes, and it really should be only once, on click.
With your approach you moved log in component body and this will cause log to execute each time element rerenders - probably 3 times: one log initially when element is loaded in DOM, and other 2 when you set states inside handler.

Related

react useState() not updating state as expected

const [refreshBtnClicked, setRefreshBtnClicked] = React.useState(false);
const refreshClicked = () => {
setRefreshBtnClicked(true);
fetchAnalytics();
}
const fetchAnalytics = async () => {
setLoading(true);
try{
let analyticsResponse = await Axios({
method: 'post',
url: process.env.REACT_APP_BEATS_GENERAL_REPORT,
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: "Bearer " + sessionStorage.getItem("idToken")
},
data: formData
})
setAnalyticsData(analyticsResponse.data);
setShowCards(true);
refreshBtnClicked && ToastSuccess('Updated successfully');
setRefreshBtnClicked(false);
setLoading(false);
}catch(err){
console.log(err);
ToastError('Error while fetching data');
setLoading(false);
}
}
i need to show the toast if refreshBtnClicked is true even though i set it to be true when the refresh button is clicked It still shows the state as false . But i am setting the state as false after the toast is displayed. can't understand y..
Because setState is asynchronous and you immediately invoke fetchAnalytics, so the new state is not yet available to it.
refreshBtnClicked doesn't need to be state at all; in fact you don't need the value at all since you can just await for the fetch to complete, and toast in the refresh clicked function.
const refreshClicked = async () => {
await fetchAnalytics();
ToastSuccess("Updated successfully");
};
const fetchAnalytics = async () => {
setLoading(true);
try {
let analyticsResponse = await Axios({
method: "post",
url: process.env.REACT_APP_BEATS_GENERAL_REPORT,
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: "Bearer " + sessionStorage.getItem("idToken"),
},
data: formData,
});
setAnalyticsData(analyticsResponse.data);
setShowCards(true);
setLoading(false);
} catch (err) {
console.log(err);
ToastError("Error while fetching data");
setLoading(false);
}
};

React fetch sending request multiple times

I'm trying fetching data in react. I call the fetch function only once but it's sending request multiple times but I don't know why. I looked other questions and tried their answers but none of them worked.
When I delete useEffect and leave the function alone, it sends a request once, but I think this is not the right way.
useEffect(() => {
fetchFunction();
}, [])
const fetchFunction =() => {
console.log("ldşsaşdlisaldi")
axios.get(
"someAPI",
{
headers: {
"Authorization" : localStorage.getItem("token")
},
credentials: 'include',
}
)
.then(res => res.json())
.then(
(result) => {
console.log(result)
setIsLoaded(true);
setTableData(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}
Don't attempt to do any sort of "initial mount" check or "state" as this is considered anti-pattern. Don't try to "outsmart" React. The double-mounting is a way for the React.StrictMode component to help you see unexpected side-effects and other issues. You should implement a cleanup function to cancel any in-flight requests when the component unmounts. Use an abortController with the axios GET request.
Example:
useEffect(() => {
const controller = new AbortController(); // <-- create controller
fetchFunction({ controller }); // <-- pass controller
return () => controller.abort(); // <-- return cleanup function
}, []);
const fetchFunction = ({ controller }) => {
axios.get("someAPI", {
headers: {
"Authorization" : localStorage.getItem("token")
},
credentials: 'include',
signal: controller.signal // <-- pass signal to request
})
.then(res => res.json())
.then((result) => {
console.log(result);
setTableData(result);
})
.catch((error) => {;
setError(error);
})
.finally(() => {
setIsLoaded(true);
});
}
For more details and explanation see Fetching Data.

update the state of my component with the response data after Post request with axios

I'm trying to update the state of my component with the response data after Post request with axios but it returns an empty array when I log out the updated state with console.log(), but shows the response.data information received with .then in axois in the broswer console. Please help me out
Code starts here
const [offers, setOffers] = useState({});//THIS IS THE STATE
const search async (e) => {
e.preventDefault();
const options = {
url: "localhost:8080/api/search",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
data,
};
axios(options)
.then((response) => {
console.log(response.data.data);// THIS RETURNS OBJECT DATA GOTTEN FROM THE SERVER AFTER POST REQUEST
setOffers(response.data.data); //IT DOES NOT UPDATE WITH RESPONSE DATA
console.log(offers); = IT RETURNS AND EMPTY ARRAY
})
.catch(function (error) {
if (error.response) {
setValerr(error.response.data.errors);
console.log(error.response);
}
});
};
thanks in advance
In react, setState is asynchronous, so when you call "setOffers" it is an asyncronous action.
Therefore when you call console.log, offers might not be updated yet.
You can read more about it here:
https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous
To listen to the value of "offers" you might need to use useEffect
An example
const [offers, setOffers] = useState({}) //THIS IS THE STATE
const search = async (e) => {
e.preventDefault()
const options = {
url: 'localhost:8080/api/search',
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
},
data,
}
axios(options)
.then((response) => {
console.log(response.data.data) // THIS RETURNS OBJECT DATA GOTTEN FROM THE SERVER AFTER POST REQUEST
setOffers(response.data.data) //IT DOES NOT UPDATE WITH RESPONSE DATA
console.log(offers)
})
.catch(function (error) {
if (error.response) {
setValerr(error.response.data.errors)
console.log(error.response)
}
})
}
useEffect(() => {
// This should log offers to the console if it has been set
if(offers) {
console.log(offers)
}
}, [offers])

React: res.json() data is undefined

I'm having issues with getting data from my fetch API. It was working previously when I had "test" inside of a class. Now that it's inside of a function, I get "undefined" when I try to console.log(data). (Note, the API call is working on the server. console.log(res.json()) returns a data. I'm LOST.
const test = () => {
fetch('/api/test/', {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
//make sure to serialize your JSON body
body: JSON.stringify({zip: val})
})
.then(res => { res.json()}) //THIS RETURNS OK
.then(data => {console.log({data})}) //THIS IS WHERE I HAVE PROBLEMS
}
EDIT:
I also tried
.then(data=> {console.log(data)})
and
.then(data => {console.log([data])})
is there something I'm missing?
Arrow_functions
You should return res.json() to work successfully;
.then(res => { return res.json()})
or
.then(res => res.json())

React Native refresh content when user hits back button - using Hooks

I would like to refresh the data, when user is back from one page to another.
This is how my useEffect function looks like now:
useEffect(() => {
setIsLoading(true);
AsyncStorage.getItem("user").then((response) => {
const currentData = JSON.parse(response);
setUser(currentData)
fetch('URL',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_id: currentData.id
}),
}
)
.then(response => response.json())
.then(data => {
setNotis(data.notifications);
setIsLoading(false)
})
.catch(error => {
});
});
}, []);
This function should run every time when user is on the page. Doesn't matter if it was onBackPressed or not.
Thanks
Using React-navigation
We can directly refresh screen if you're using react-navigation
Import #react-navigation/native
import React, { useEffect } from "react";
import { useIsFocused } from "#react-navigation/native";
const HomeScreen = (props) => {
const isVisible = useIsFocused();
useEffect(() => {
console.log("called when screen open and also on close");
// this will call on both screen open and screen close.
if (isVisible) {
console.log("called when screen open or when back on screen ");
}
}, [isVisible]);
return (
......
)
}
I hope it will help.
The real problem here is that the screen is not being unmounted when navigating outside of it, so the hook won't fire since the component is already mounted. There are multiple options to solve this issue just as adding a listener when the screen gets focused/blurred or just watch for the changes for the navigation prop. For the last workaround, you could try something like:
useEffect(() => {
setIsLoading(true);
AsyncStorage.getItem("user").then((response) => {
const currentData = JSON.parse(response);
setUser(currentData)
fetch('URL',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
user_id: currentData.id
}),
}
)
.then(response => response.json())
.then(data => {
setNotis(data.notifications);
setIsLoading(false)
})
.catch(error => {
});
});
}, [navigation]);
For watching onFocus event, you could import NavigationEvents from react-navigation and move the logic for the hook inside a function refreshData
import {NavigationEvents} from 'react-navigation`
...
<NavigationEvents onWillFocus={refreshData}/>
Also, you should be setting the isLoading state to false whenever the Promise has settle, for instance you could use
.finally(() => {
setIsLoading(false)
})

Resources