I want to make Component that Fetching Data from REST API with repeatedly.
I noticed There is some problem with setInterval and React.js.
so I coded recursive way with setTimeout and useEffect.
It seems work well though, I am curious about necessity of clearTimeout.
Is there no performance Issue cause of no clearTimeout or else?
ps: Sorry for my poor English.
import { useEffect, useState } from "react";
import axios from "axios";
function RepeatFetch() {
const [data, setData] = useState(null);
const repeatFetchData = async () => {
try {
const response = await axios.get(
"https://api.foo.com/"
);
setData(response.data);
} catch (e) {
}
setTimeout(repeatFetchData, 3000);
};
useEffect(() => {
responseFetchData();
// return () => clearTimeout(timer); // Do I need clearTimeout? then, how?
}, []);
return (
<div>
{data}
</div>
);
}
export default RepeatFetch;
Related
Pardon me if this is a silly question. Im a new react learner. Im trying using a create react app. I am using a custom hook for API handling only. Now I want the useEffect to run only when the data changes. Thats why I put it in dependency. But yet it keeps rendering for infinity. What is the problem? Or how should I handle this?
Thank you.
import { useCallback, useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
const getDogCollection = useCallback(() => {
fetch(url, options)
.then((res) => res.json())
.then((result) => {
console.log(data, "----DI---", result);
setData(result);
});
}, []);
useEffect(() => {
getDogCollection();
}, [data]);
return data;
};
You'll just want url and options to be the dependencies, not data (because you set it in the effect!).
import { useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url, options)
.then((res) => res.json())
.then(setData);
// TODO: add error handling...
}, [url, options]);
return data;
};
However, you'll probably just want to look at swr instead of writing this hook yourself.
It's because you've given data as one of the dependencies, but the function called in your useEffect updates data, so it then runs again.
You can change it to the length, like this, and it should work:
useEffect(() => {
getDogCollection();
}, [data.length]);
I am getting this same error whether I use useEffect inside of my function component or out of it:
React Hook "useEffect" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function
import React, { useEffect } from "react";
const GetVerse = async () => {
useEffect(async () => {
const fetchVerse = await fetch(
"https://bhagavad-gita3.p.rapidapi.com/v2/chapters/1/verses/null/"
);
const verseBody = await fetchVerse.json();
console.log(verseBody);
});
return <div></div>;
};
export default GetVerse;
You component can't be async. But if you want to have an async useEffect, you have to do like this :
const GetVerse = () => { // component can't be async
useEffect(() => { // useEffect callback can't be async either
const fn = async () => { // declare an async function here
const fetchVerse = await fetch(url)
const verseBody = await fetchVerse.json()
console.log(verseBody);
}
fn() // call the async function
}, []) // don't forget to add dependency array here
return <div></div>
}
React Components and the useEfffect callback shouldn't be async. Here is a way to use await in the callback. The idea is to create a async function and then call it.
Also consider adding an empty array [] as the second parameter to useEffect as not putting this will cause infinite API calls.
import React, { useEffect } from "react";
const GetVerse = () => {
useEffect(() => {
async function getData() {
const fetchVerse = await fetch(
"https://bhagavad-gita3.p.rapidapi.com/v2/chapters/1/verses/null/"
);
const verseBody = await fetchVerse.json();
console.log(verseBody);
}
getData();
}, []);
return <div></div>;
};
export default GetVerse;
I wrote a simple code that requests data from a local json server when the page loads. I have this code repeated in several places and I want to put it in the custom hook.
Tell me how to write and apply a custom hook correctly?
const [data, setData] = useState()
useEffect(() => {
const getPosts = async () => {
try {
setData(await getData('http://localhost:3001/posts'))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getPosts()
}, [])
I tried to write like this, but it doesn't work:
import { useEffect, useState } from "react"
import { getData } from './../helpers'
const useData = url => {
const [currentData, setCurrentData] = useState()
useEffect(() => {
const getCurrentData = async () => {
try {
setCurrentData(await getData(url))
} catch (error) {
console.log('ERROR >>', error.message)
}
}
getCurrentData()
}, [url])
return currentData
}
export default useData
Please check this sandbox, this seems to load data once every page loads. I just don't pass the URL inside useEffect because I only want to call it once on page load.
https://codesandbox.io/s/dank-sky-6fs4rq?file=/src/useData.jsx
From what I understand useEffect hook runs last as a sideEffect. I am attempting to console log data.main.temp. I can understand that it doesn't know what that is yet, because it is fetching the data from the api in the useEffect hook which runs after.
How would I be able to access or console log data.main.temp AFTER the api call? (I feel like setTimout is the cheating way?)
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
export default function Weather() {
//State Management//
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
useEffect(() => {
const fetchData = async () => {
//get coordinates//
navigator.geolocation.getCurrentPosition(function (position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
//fetch openWeather api//
await fetch(`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`)
.then((res) => res.json())
.then((result) => {
setData(result);
console.log(result);
});
};
fetchData();
}, [lat, long]);
//Examples of what I want, they run too early before api//
console.log(data.main.temp);
const Farenheit = data.main.temp * 1.8 + 32;
return (
<Card>
{typeof data.main != "undefined" ? (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
) : (
<div></div>
)}
</Card>
);
}
You're right, the effect function is run after the first render which means you need to wait somehow until your api call is done. One common way to do so is to introduce another state flag which indicate whether the data is available or not.
Another thing which does not follow react good practices is the fact that you're effect function does more than one thing.
I also added trivial error handling and cleaned up mixed promises and async await
here is your refactored code
import React, { useState, useEffect } from "react";
import Button from "../UI/Button";
import styles from "./Weather.module.css";
import moment from "moment";
import Card from "../UI/Card";
//openWeather API key
const key = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
export default function Weather() {
//State Management//
const [lat, setLat] = useState();
const [long, setLong] = useState();
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
}, []);
useEffect(() => {
const fetchData = async () => {
if (lat && long && key) {
try {
setLoading(true);
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${key}`
);
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}
};
fetchData();
}, [lat, long]);
if (error) {
return <div>some error occurred...</div>;
}
return (
<Card>
{loading || !data ? (
<div>loading...</div>
) : (
<div className={`${styles.weatherContainer} ${styles.clouds}`}>
<h2>Weather</h2>
<p>{data.name}</p>
<p>{data.main.temp * 1.8 + 32} °F</p>
<p>{data.weather[0].description}</p>
<hr></hr>
<h2>Date</h2>
<p>{moment().format("dddd")}</p>
<p>{moment().format("LL")}</p>
</div>
)}
</Card>
);
}
You can use another useEffect, which depends on changing the data state
useEfect(() => {
if (data) {
// do something with data
}
}, [data])
You can create a simple function and call it in your API call response and pass in the data directly from the api response, that way you will have access to the data immediately there's a response.
E.g
...
.then((result) => {
setData(result);
getDataValue(result) // this function will be called when the response comes in and you can use the value for anything
console.log(result);
});
METHOD 2:
You can use a useEffect hook to monitor changes in the data state, so that whenever there's an update on that state, you can use the value to do whatever you want. This is my less preferred option.
useEffect(() => {
//this hook will run whenever data changes, the initial value of data will however be what the initial value of the state is
console.log(data) //initial value = [] , next value => response from API
},[data])
While I fetch data from API and set the response to a array using useEffect
it call the API repeat continuous.
let [product, setproduct] = useState([]);
async function fetchData() {
let response = await axios(
`api`
);
let user = await response.data;
setproduct(user);
console.log(product);
}
useEffect(() => {
fetchData();
});
From the docs,
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
You can provide the empty dependency array / [] as second argument to useEffect, it is same as componentDidMount which will executes only once in component life cycle.
useEffect(() => {
fetchData();
}, []); //This will run only once
Pass empty [] as an second argument to useEffect method. It will be called on initial render, like below.
useEffect(() => {
fetchData();
}, []);
i think the blow example will help you through fetch API .
import React , {useEffect} from "react";
import axios from "axios";
const Courses = ()=>{
useEffect(()=>{
getProducts()
})
const getProducts = async() => {
await axios.get('api/get_all_products')
.then(({data}) =>{
console.log("this is data from api ");
console.log('data' , data);
} )
console.log("data ehre ");
}
return(
<div>
<h2>Products data here</h2>
</div>
)
};
export default Courses;
let [product, setProduct] = useState([]);
// This function will be only called once
async function fetchData() {
let response = await axios("api");
let user = response.data;// Don't need await here
setProduct(user);
console.log(product);
}
useEffect(() => {
fetchData();
}, []);