How to call an async function inside a UseEffect() in React? - javascript

I would like to call an async function and get the result for my UseEffect.
The fetch api examples i found on the internet are directly made in the useEffect function.
If my URL changes, i must patch all my fetchs.
When i tried, i got an error message.
This is my code.
async function getData(userId) {
const data = await axios.get(`http://url/api/data/${userId}`)
.then(promise => {
return promise.data;
})
.catch(e => {
console.error(e);
})
return data;
}
function blabla() {
const [data, setData] = useState(null);
useEffect(async () => {
setData(getData(1))
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}
index.js:1375 Warning: An effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state

Create an async function inside your effect that wait the getData(1) result then call setData():
useEffect(() => {
const fetchData = async () => {
const data = await getData(1);
setData(data);
}
fetchData();
}, []);

If you're invoking it right-away you might want to use it as an anonymous function:
useEffect(() => {
(async () => {
const data = await getData(1);
setData(data);
})();
}, []);

It would be best if you did what the warning suggests - call the async function inside the effect.
function blabla() {
const [data, setData] = useState(null);
useEffect(() => {
axios.get(`http://url/api/data/1`)
.then(result => {
setData(result.data);
})
.catch(console.error)
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}
If you want to keep the api function outside of the component, you can also do this:
async function getData(userId) {
const data = await axios.get(`http://url/api/data/${userId}`)
.then(promise => {
return promise.data;
})
.catch(e => {
console.error(e);
})
return data;
}
function blabla() {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
const newData = await getData(1);
setData(newData);
})();
}, []);
return (
<div>
this is the {data["name"]}
</div>
);
}

Since getData returns a Promise you could just use .then
useEffect(() => {
getData(1).then(setData);
}, []);

Component might unmount or re-render with different someId before await is resolved:
const unmountedRef = useRef(false);
useEffect(()=>()=>(unmountedRef.current = true), []);
useEffect(() => {
const effectStale = false; // Don't forget ; on the line before self-invoking functions
(async function() {
// You can await here
const response = await MyAPI.getData(someId);
/* Component has been unmounted. Stop to avoid
"Warning: Can't perform a React state update on an unmounted component." */
if(unmountedRef.current) return;
/* Component has re-rendered with different someId value
Stop to avoid updating state with stale response */
if(effectStale) return;
// ... update component state
})();
return ()=>(effectStale = true);
}, [someId]);
Consider using Suspense for data that needs to be loaded before component is mounted.

You can still define the async function outside of the hook and call it within the hook.
const fetchData = async () => {
const data = await getData(1);
setData(data);
}
useEffect(() => {
fetchData();
}, []);

Related

I tried this code i already passed dependency in useEffect but the api call again and again after 3/4 second

Below code i try to make get response from api and put in Movie Component. but the problem is that api hit again and again i don't why this happened.here screen shot of api call
const [loading, setloading] = useState(false);
const [movielist, setmovielist] = useState([]);
const [err, seterr] = useState("");
const fetchMoviesHandler = useCallback(async () => {
setloading(true);
try {
const reponse = await fetch("https://swapi.dev/api/films");
if (reponse.status != 200) {
seterr("something is wrong");
}
const data = await reponse.json();
const result = data.results;
setmovielist(result);
} catch (errr) {
}
});
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
return (
<div>
{movielist.map((movie) => {
return <Movie key={movie.episode_id} title={movie.title} />;
})}
</div>
);
};
This is returning a new instance of the function on every render:
const fetchMoviesHandler = useCallback(async () => {
// your function
});
Which will trigger the useEffect on every render, since this function is in its dependency array.
To tell useCallback to memoize the function and keep the same instance across multiple renders, it also needs a dependency array. For example:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, [setloading, setmovielist, seterr]);
Or, at the very least, an empty dependency array:
const fetchMoviesHandler = useCallback(async () => {
// your function
}, []);
Which would essentially create one instance of the function and always use it for the life of the component.

Warning: Can't perform a React state update on an unmounted component , method needs to run only one time

I'm trying to fetch the advertisers list when the component mounts, but it is causing me memory leak,
Any suggestions ? i'll be grateful :)
useEffect(() => {
const fetchData = async () => {
const list = await Api.listAdvertisers();
setAdvertisers(list);
};
fetchData();
}, []);
You can declare your fetchData function on main scope and you can check the function if it is called or not by declaring a didMount variable in useEffect scope.
const didMount = useRef(false);
const fetchData = async () => {
const list = await beeswaxService.listAdvertisers();
setAdvertisers(list);
return;
};
useEffect(() => {
if(!didMount.current){
didMount.current = true;
fetchData();
}
}, []);
Maybe the function inside the useEffect is causing memory leak, try this?
const fetchData = async () => {
const list = await beeswaxService.listAdvertisers();
setAdvertisers(list);
return;
};
useEffect(() => {
fetchData();
}, []);

calling useFetch hook inside useEffect

I found a very interesting hook and I want to use this hook inside a useEffect (it goes against the rules)
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
function App() {
const res = useFetch("example.com", {});
useEffect(() => {
// use the hook and make a secondary request
}, [])
what modifications do I need to make in order to support this? AFAIK hooks can´t be called inside useEffect
maybe a new parameter that will setUrl and run it again?
Seems like you want to execute the fetch request in the hook when some state or variable changes.
You cannot conditionally call a hook, nor can you execute it inside another hook accordinng to the rules of hooks mentioned in the documentation.
To do what you want, you can modify your custom hook to accept an array as a dependency that you pass on to the useEffect inside it and it will call the api when any of the dependency changes
const useFetch = (url, options, deps = []) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
url && fetchData();
}, deps);
return { response, error };
};
and use it like
function App() {
const res = useFetch("example.com", {}, [someVariable]);
...
}
You can't call useFetch conditionally nor can it be called in any callback (i.e. a useEffect callback) (see rules of hooks), but you can take advantage of the fact that hooks are called in the same order each render. Do the conditional test and set the URL that is passed to the second useFetch hook. Update the useFetch hook to check for truthy url before making the request.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
url && fetchData();
}, []);
return { response, error };
};
...
function App() {
const res = useFetch("example.com", {});
let url = "";
if (someCondition) {
let url = ""example2.com"";
}
const res2 = useFetch("example.com", {});

How can I return only the set-State from my fetched json data?

im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};

Function to get data keep running in loop

I have a function that gets some data from my backend and then I want simply to assign it to the state and display it in my browser. Everything works correctly, but I don't know why when I run a request the function keeps calling the API without stopping. What is the reason for this?
It seems that the function is stuck in some kind of while-true loop.
function App() {
const [orders, setOrders] = useState();
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;
What is the reason for this?
This happens because you are calling a function every render inside the functional component.
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response); // this will re render the component
console.log(response);
};
getOrders(); // this will be called every render and cause the infinity loop
When you render the component, you call getOrders and this functions calls setOrders wich will rerender the component, causing a infinity loop.
First render => call getOrders => call setOrders => Rerender =>
Second render => call getOrders => call setOrders => Rerender =>
...
You need to use useEffect hook or call the function on some event (maybe button click)
e.g. using useEffect
function App() {
const [orders, setOrders] = useState(null);
useEffect(() => {
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
}, []);
return <div className="App">{JSON.stringify(orders)}</div>;
}
You want to use the Effect hook React offers:
https://reactjs.org/docs/hooks-effect.html
function App() {
const [orders, setOrders] = useState();
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
useEffect(() => {
getOrders();
}, []); //Empty array = no dependencies = acts like componentDidMount / will only run once
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;
I think you are using react-hooks here, so your getOrders function is getting render all the time.
Use useEffect from react to avoid this.
function App() {
const [orders, setOrders] = useState();
useEffect(() => {
const getOrders = async () => {
const response = await axios.get("/api/orders/");
setOrders(response);
console.log(response);
};
getOrders();
}, [])
return <div className="App">{JSON.stringify(orders)}</div>;
}
export default App;

Categories

Resources