I'm new to react and I'm learning how to use useEffect. I encountered this warning in my react app. I tried out some solutions on SO but the warning still remains. Both fetchUser and fetchPosts trigger this warning. Can anyone enlighten me what is the problem and what does the warning mean?
App.js
useEffect(() => {
setLoading(true)
const getUser = async () => {
const userFromServer = await fetchUser()
if (userFromServer) {
setUser(userFromServer)
setLoading(false)
} else {
console.log("error")
}
}
getUser()
}, [userId])
useEffect(() => {
const getPosts = async () => {
const postsFromServer = await fetchPosts()
setPosts(postsFromServer)
}
getPosts()
}, [userId])
useEffect(() => {
const getUserList = async () => {
const userListFromServer = await fetchUserList()
setUserList(userListFromServer)
}
getUserList()
}, [])
// Fetch user
const fetchUser = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
const data = await res.json()
return data
}
// Fetch posts
const fetchPosts = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
const data = await res.json()
return data
}
// Fetch list of users
const fetchUserList = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/')
const data = await res.json()
return data
}
If you are using any function or state which has been declared outside the useEffect then you need to pass it in the dependency array like this:
const someFunctionA = () => {
....
}
const someFunctionB = () => {
....
}
useEffect(() => {
....
}, [someFunctionA, someFunctionB])
You can read more about it here in case you want to know how it will be rendered: React useEffect - passing a function in the dependency array
Related
I'm trying to understand how useEffect works.
I have two callApi: "callApiDialer" is based on response of "callApiManager", for get id from list.
But "currentLeadId" state at first called obviously is null.
How can call "callApiDialer" when currentLeadId is not null?
import React, { useState, useEffect } from 'react';
const [loading, setLoading] = useState(true);
const [apiManager, setApiManager] = useState([]);
const [apiDialer, setApiDialer] = useState([]);
const [currentLeadId, setCurrentLeadId] = useState(null);
// CALL API
const callApiManager = async () => {
try {
const response = await api.get(`/api/manager/op/1`);
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
} catch (err) {
alert("fetchApiManager " + err.response.status);
}
}
const callApiDialer = async () => {
try {
const response = await api.get(`/api/manager/lead/${currentLeadId}`);
setApiDialer(response.data.lead);
setLoadingModal(false);
} catch (err) {
alert("fetchApiSources " + err.response.status);
}
}
useEffect(() => {
callApiManager();
}, [])
useEffect(() => {
console.log(currentLeadId); // <-- here get first null and after currentLeadId
if(currentLeadId) {
callApiDialer();
setLoading(false);
}
}, [currentLeadId])
You could have just one function that call both, therefore there would be only one useEffect.
// CALL API
const callBothApisAtOnce= async () => {
try {
const op = await api.get(`/api/manager/op/1`);
const response = await api.get(`/api/manager/lead/${op.data.dialer_list[0].id}`);
// rest of your logic...
} catch (err) {
alert("err" + err);
}
}
useEffect(() => {
callBothApisAtOnce()
}, [])
you can use axios's promise base functionality
axios.get(`/api/manager/op/1`).then(res => {
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
axios.get(`/api/manager/lead/${response.data.dialer_list[0].id}`).then(res1 =>{
setApiDialer(res1.data.lead);
setLoadingModal(false);
}
}
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 use useEffect with some async functions, however when the dependency in the dependency array changes, only the non async code gets called in useEffect.
useEffect( async () => {
console.log(account);
const web3 = await getWeb3();
const acc = await loadAcc(web3);
await loadContract(web3, acc);
}, [account])
When my account state variable changes, useEffect gets invoked again, but only the console.log(account) statement will get executed.
How should I work around this problem?
The function passed into useEffect cannot be async. You can define an async function inside the useEffect and then use the then/catch syntax.
Further, also pass in all the functions that are defined outside of the useEffect (that you are calling) as a dependency to useEffect
useEffect(() => {
const myAsyncFunc = async () => {
console.log(account);
const web3 = await getWeb3();
const acc = await loadAcc(web3);
await loadContract(web3, acc);
}
myAsyncFunc.catch(console.error);
}, [account, getWeb3, loadAcc, loadContract])
useEffect expected to return either void or a function( the cleanup function ). When you make the function you pass to useEffect as an async, the function will return a promise.
One way to do it is,
useEffect( () => {
const init = async () => {
const web3 = await getWeb3();
const acc = await loadAcc(web3);
const res = await loadContract(web3, acc);
// do something after the async req
}
init();
}, [getWeb3, loadAcc, loadContract])
Or else,
const [web3, setWeb3] = useState(null);
const [acc, setAcc] = useState(null);
useEffect(() => {
getWeb3();
}, [getWeb3])
useEffect(() => {
if (!web3) return;
loadAcc(web3);
}, [web3, loadAcc])
useEffect(() => {
if (acc && web3) {
loadContract(acc, web3);
}
}, [acc, web3, loadContract])
const getWeb3 = useCallback(async () => {
// do some async work
const web3 = // async call
setWeb3(web3)
}, [])
const loadAcc = useCallback(async (web3) => {
// do some async work
const acc = // async call
setAcc(acc);
}, [])
const loadContract = useCallback(async (acc, web3) {
// do anything
}, [])
I have used match.params.id but recent update is not helping.
import { getPost } from '../../service/api';
const DetailView =({ match }) => {
const classes = useStyle();
const url = 'https://images.';
const [post, setPost] = useState({});
useEffect(() => {
const fetchData = async () => {
let data = await getPost(match.params.id);
console.log(data);
setPost(data);
}
fetchData();
}, []);
This is another calling by id code Inside another post-controller.js
export const getPost = async (request, response) => {
try {
const post = await Post.findById(request.params.id);
response.status(200).json(post);
} catch (error) {
response.status(500).json(error)
}
}
Try to use the useParams hook from react-router-dom:
import { useParams } from 'react-router-dom';
const DetailView =() => {
const { id } = useParams();
useEffect(() => {
const fetchData = async () => {
let data = await getPost(id);
...
}
fetchData();
}, []);
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]
)