i'm building a simple react app that fetches the data from the OpenWeather Api. i want to refresh the data received every minute to reflect the changes(if there are any changes) to the app. I tried using setInterval when i call the fetchApi function that i created, but according to the console log it doesn't sound very precise or realiable. This is my part of the code:
useEffect(() => {
const currentData = async () => {
const currentWeatherData = await fetchCurrentData();
setCurrentWeather(currentWeatherData);
};
const futureData = async () => {
setFutureWeather(await fetchFutureData())
console.log(futureWeather);
};
currentData();
futureData();
setInterval(() => {
currentData();
futureData();
console.log("reloaded!");
}, 60000);
}, []);
How can i improve this code to make it effectively work?
Thanks guys
I guess that you will need to clear the interval in the cleanup of the useEffect function.
useEffect(() => {
const currentData = async () => {
const currentWeatherData = await fetchCurrentData();
setCurrentWeather(currentWeatherData);
};
const futureData = async () => {
const futureWeatherData = await fetchFutureData();
setFutureWeather(futureWeatherData);
};
currentData();
futureData();
const intervalId = setInterval(() => {
currentData();
futureData();
}, 60000);
return () => {
clearInterval(intervalId);
};
}, []);
Related
I have to functions/const to get data from API:
const [isLoadingRoom, setLoadingRoom] = useState(true);
const [isLoadingLobby, setLoadingLobby] = useState(true);
const [rooms, setRooms] = useState([]);
const [lobbies, setLobbies] = useState([]);
const getRooms = async () => {
let isMounted = true;
async function fetchData() {
const response = await fetch(link);
const json = await response.json();
// 👇️ only update state if component is mounted
if (isMounted) {
setRooms(json);
setLoadingRoom(false);
}
}
fetchData();
return () => {
isMounted = false;
}
}
const getLobbies = async () => {
let isMounted = true;
async function fetchData() {
const response = await fetch(link);
const json = await response.json();
// 👇️ only update state if component is mounted
if (isMounted) {
setLobbies(json);
setLoadingLobby(false);
}
}
fetchData();
return () => {
isMounted = false;
}
}
useEffect(() => {
const roomInterval = setInterval(() => {
getRooms();
getLobbies();
}, 5000);
return () => clearInterval(roomInterval);
}, []);
The API gets data every 5 second, but after a while I get this message:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I have tried different approaches to fetch the API with const, functions, async etc. but I get this error message anyway.. Any tips?
useRef rather than normal variable:
const isMountedRef = useRef(true);
useEffect(() => {
const roomInterval = setInterval(() => {
getRooms();
getLobbies();
}, 5000);
return () => {
clearInterval(roomInterval);
isMountedRef.current = false;
};
}, []);
and change check conditions to
if(isMountedRef.current){
// execute setState
}
Hope it helps. feel free for doubts
I'm trying to stop axios request. I use useInterval(custom hooks)(I referenced a website) to request api.
So I stop it with useState and it's totally stopped when i set interval like 1000ms.
however, when i set interval like 100ms then i can't stop api request. it's stopped after 3seconds or something.
So i tried to use if statement. but it's not working as i expected.
and I also checked Network from development tool on chrome
and the request Status was getting changed from pending to 200
and when all the request's Status change to 200, then it stopped.
I really want to know how i can stop API request properly.
my code is like this
useInterval
import { useEffect } from "react";
import { useRef } from "react";
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;
API calling
const [API_DATA, setAPI_DATA] = useState(null);
const [apiStart, setApiStart] = useState(false);
const [spinner, setSpinner] = useState(false);
//Request API
const getAPI = useCallback(async () => {
if (apiStart) {
await axios
.get(API_URL, {
headers: Header,
})
.then(response => {
setAPI_DATA(response.data);
setSpinner(false);
})
.catch(error => {
init();
console.log("error");
});
}
}, [API_DATA, spinner]);
// start API
const start_API = () => {
setSpinner(true);
setApiStart(true);
};
//stop API
const stop_API = () => {
setSpinner(false);
alert("API STOP");
setApiStart(false);
};
//using useInterval
useInterval(
() => {
if (apiStart) return getAPI();
},
apiStart ? 100 : null
);
I'm trying to stop axios request.
I use useInterval(custom hooks)(I referenced a website) to request api.
So I stop it with useState and it's totally stopped when i set interval like 1000ms.
however, when i set interval like 100ms then i can't stop api request. it's stopped after 3seconds or something.
So i tried to use if statement. but it's not working as i expected.
and I also checked Network from development tool on chrome
and the request Status was getting changed from pending to 200
and when all the request's Status change to 200, then it stopped.
I really want to know how i can stop API request properly.
my code is like this
useInterval
import { useEffect } from "react";
import { useRef } from "react";
const useInterval = (callback, delay) => {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;
API calling
const [API_DATA, setAPI_DATA] = useState(null);
const [apiStart, setApiStart] = useState(false);
const [spinner, setSpinner] = useState(false);
//Request API
const getAPI = useCallback(async () => {
if (apiStart) {
await axios
.get(API_URL, {
headers: Header,
})
.then(response => {
setAPI_DATA(response.data);
setSpinner(false);
})
.catch(error => {
init();
console.log("error");
});
}
}, [API_DATA, spinner]);
// start API
const start_API = () => {
setSpinner(true);
setApiStart(true);
};
//stop API
const stop_API = () => {
setSpinner(false);
alert("API STOP");
setApiStart(false);
};
//using useInterval
useInterval(
() => {
if (apiStart) return getAPI();
},
apiStart ? 100 : null
);
Go take a look at the axios documentation at https://axios-http.com/docs/cancellation. I would remove the if(apiStart) as this does not do much. I would possibly rewrite your this method as follows:
const [data, setData] = useState(null);
const [spinnerActive, setSpinnerActive] = useState(false);
const controller = new AbortController();
const getAPI = useCallback(async () => {
setSpinnerActive(true);
await axios
.get(API_URL, {
headers: Header,
signal: controller.signal
})
.then(response => {
setData(response.data);
setSpinnerActive(false);
})
.catch(error => {
setSpinnerActive(false);
console.log("error");
});
}, [data, spinnerActive]);
useInterval(
() => {
getApi()
},
apiStart ? 100 : null
);
Then when you want to abort the request, call controller.abort()
I recently used hooks with React to fetch data from server but i'm facing a problem with hooks. The code seems correct but it look like the useEffect isn't called at first time but 3 seconds after with the setInterval. I have blank table for 3 seconds before it appear. I want to directly show the data and call it 3 seconds later.
What is the correct way to use it ?
const [datas, setDatas] = useState([] as any);
useEffect(() => {
const id = setInterval(() => {
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
fetchData();
}, TIME)
return () => clearInterval(id);
}, [])
You need to invoke fetchData once initially outside the interval. Define fetchData outside the interval.
useEffect(() => {
// (1) define within effect callback scope
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
const id = setInterval(() => {
fetchData(); // <-- (3) invoke in interval callback
}, TIME);
fetchData(); // <-- (2) invoke on mount
return () => clearInterval(id);
}, [])
With React Hooks:
const [seconds, setSeconds] = useState(0)
const interval = useRef(null)
useEffect(() => { if (seconds === 60) stopCounter() }, [seconds])
const startCounter = () => interval.current = setInterval(() => {
setSeconds(prevState => prevState + 1)
}, 1000)
const stopCounter = () => clearInterval(interval.current)
How would one go about using the useEffect hook to replace both componentDidMount and componentWillUnmount while working with Firebase? I can't find a solution to this 'unsubscribe' function.
unsubscribe = null;
componentDidMount = async () => {
this.unsubscribe = firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
this.setState({ posts })
})
}
componentWillUnmount = () => {
this.unsubscribe()
}
Here's what I tried:
useEffect(() => {
async function getSnapshot() {
const unsubscribe = firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
setPosts(posts)
}
getSnapshot()
//return something to clear it? I don't have access to 'unsubscribe'
}, [])
You are actually pretty close with your answer. You weren't using await in your function, so there was no point in using it.
useEffect(() => {
const unsubscribe = firestore.collection('posts').onSnapshot((snapshot) => {
const posts = snapshot.docs.map(...)
setPosts(posts);
});
return () => {
unsubscribe();
};
}, []);
If you did need to use async, you can just utilize the closure to get unsubscribe out of the async function.
useEffect(() => {
let unsubscribe;
async function getSnapshot() {
unsubscribe = firestore.collection('posts').onSnapshot((snapshot) => {
const posts = snapshot.docs.map(...)
setPosts(posts);
});
}
getSnapshot();
return () => {
unsubscribe();
};
}, []);
you're probably going to run into trouble using async inside useEffect, check out https://www.npmjs.com/package/use-async-effect
useAsyncEffect( async() => {
const unsubscribe = await firestore.collection('posts').onSnapshot(snapshot => {
const posts = snapshot.docs.map(...)
setPosts(posts)
}
return () => {
console.log("unmount")
unsubscribe()
};
}, [])
EDIT: actually it seems from the docs that you don't need async at all there:
have you tried this format?
useEffect(
() => {
const unsubscribe = firebase
.firestore()
.collection('recipes')
.doc(id)
.collection('ingredients')
.onSnapshot( snapshot => { const ingredients = [] snapshot.forEach(doc => { ingredients.push(doc) }) setLoading(false) setIngredients(ingredients) }, err => { setError(err) } )
return () => unsubscribe()
},
[id]
)