So basically im making a CRUD app using react and firebase firestore for the backend.
My write and delete operation is doing well, there is no problem with it.
But my read operation have problem.
My web is getting all document from a collection in firebase using useEffect. So this only run whenever it first mount (when my web load first time) and when im changing "users" value when doing delete and create operation
this my code:
useEffect(() => {
const getUsers = async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
};
getUsers();
}, [users]);
idk whats wrong but im getting a very high read operation when im test the web, its like every one read operation i do in my website, its getting like hundred operation in the firebase. i can see this in my firebase console, when im using the web just like 5 minute in my firebase console the read operation reaching 20k< operation.
can anyone help me how to deal with this, thanks!
You dont show all of your code here, so I will need to do some guessing.
Your useEffect has a dependency array that now is set to [users]. This means that every time the variable users changes your useEffect will rerender. Inside your useEffect you then set a new value to users by the setUsers function. Even if you get the same values returned from firebase regarding the current users, you still create a new array each time you read data. (querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id}))). React only does a shallow comparison, meaning that the object reference has changed, and therefore users is different on each render.
First you need to decide when you want to run the useEffect and what should trigger it. If changes in the variable users is not the correct place to check, then I would remove users from the dependency array.
One solution could be to move the functionality in your effect into its own function and wrap it in an useCallbac. You can then call this function from an ´useEffect` on initial load, and after that simply load the effect whenever you delete or create users. Something like this.
const getUsers = useCallback(async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
}, [collection])
useEffect(() => {
getUsers()
}, [getUsers]);
const createUser = () => {
...
getUsers()
}
const deleteUser = () => {
...
getUsers()
}
(PS! I would recommend adding the eslint-plugin-react-hooks to your eslint-config. This will give you some warning if your hooks are used wrong)
Related
I have a page that displays data fetched from a MongoDb through API, in this page, you can modify the data and after that, the page will render again to display the new data. But inspecting the network requests I noticed my react app sends an infinite number of requests, which obviously slows down everything. I read this is caused by this snippet of code:
useEffect(() => {
fetchData();
}, [users]);
I also read I must empty the dependencies array of the useEffect, but If I do so, the page will not re-render if the data changes (for example after inserting a new record in the db).
This is the function I use to get the data from the db:
const [users, setUsers] = useState([]);
async function fetchData() {
const res = await fetch("http://localhost:8000/users/");
if (res.status === 401) {
console.log(res.json);
} else {
setUsers(await res.json());
}
}
How can I fix this? Thanks.
You created an infinite loop:
fetchData calls setUsers, which sets users. The effect reacts to changes to users, and calls fetchData again. ♾️
I don't know your exact use case, but one solution would be to only call fetchData when an actual user interaction has happend in your app that makes you want to fetch new data.
In my project I use ReactJS in combination with redux and firebase.
Creating a thunk to make async calls to firebase and store the data in redux.
When I'm fetching my files from firebase storage.
Using this method:
try {
let list = [];
await storage
.ref()
.child(path)
.listAll()
.then((res) => {
res.items.forEach((item) => {
storage
.ref()
.child(item.fullPath)
.getDownloadURL()
.then((urlRes) => {
list.push({
name: item.name,
url: urlRes,
});
});
});
});
dispatch(getFileActionSuccess(list));
This method works as intended.
It returns an array of files with their url to view/download them.
The problem is when I try to access this object in my state, it returns an empty array.
Even though when checking using Redux Devtools, I can clearly see that after the list was dispatched. And I could see the correct data.
Devtools image
Note: this is not the real code but a representation
function page() {
getFiles();
<filesList/>
}
function filesList() {
const files = useSelector((state) => state.files, _.isEqual);
console.log(files);
return (..insert render..);
}
But when logging the files. It shows an empty array at first. But when expanding it, it shows the correct data. But it doesn't render it. As I don't understand why it isn't showing like it is supposed to I no longer know what to do and how to fix this.
Simply fetch the data on component mount and component update, and update your state accordingly.
If you’re using React Hooks, you can use React.useState() and give it a dependency. In this case the dependency would be the part of your state which will update upon completion of your HTTP request.
I use the react-query library to get my data.
When the user changes, I would love it if the previous user data was removed automatically & new data was fetched.
This does, not happen though, the api gets called a couple times more with the old userId, and only after 1 or 2 times re-focussing on the app, it will fetch the data with the proper new userId.
Here is the hook:
When I log some info in the getData function, I can see it being called a couple times with the old userId after logging out.
export default function useData() {
const {user} = useAuth();
const queryClient = useQueryClient();
useEffect(() => {
queryClient.removeQueries('data')
queryClient.invalidateQueries()
}, [user]);
return useQuery('data', () => getData(user!.uid), {
enabled: !!user,
})
}
Does anyone know how I can remove all data when my user changes?
All dependencies of your query should be in the query key, because react-query automatically refetches when the key changes. This is also in the docs here.
In your case, this means adding the user id:
useQuery(
['data', user?.uid],
() => getData(user!.uid),
{
enabled: !!user,
}
)
For clearing up old cache entries, I would possibly suggest setting cacheTime: 0 on your query, which means they will be garbage collected as soon as the last observer unmounts. Calling queryClient.removeQueries manually is also an option.
I'm making a React dashboard that calls an API every minute for updates. Following the many answers in SO, I have this at the moment that sort of works:
const Dashboard = (props) => {
const [stockData, setStockData] = useState([]);
useEffect(() => {
//running the api call on first render/refresh
getAPIData();
//running the api call every one minute
const interval = setInterval(() => {
getAPIData()
}, 60000);
return () => clearInterval(interval);
}, []);
//the api data call
const getAPIData = async () => {
try {
const stdata = await DataService.getStockData();
setStockData(stdata);
}
catch (err) {
console.log(err);
}
};
However I keep getting browser warning
React Hook useEffect has a missing dependency: 'getAPIData'. Either include it or remove the dependency array
Is this a cause for concern (e.g. causing memory leaks)?
Ive tried to fix it:
It doesnt seem possible to not use useEffect (use setInterval directly)
If I remove dependency or put stockData as dependency for useEffect,
I'll see the API call being made every second, so I assume thats not
right.
If I put the api data call block directly in useEffect, the page wont
have the data shown when it loads the first time/or the page refreshed and I have to wait
for a minute.
I found several references on the issue such as
here
and
here
but I couldn't comprehend it given that I've only started using React
a month ago.
Appreciate any help for this!
You can resolve this issue in multiple way:
You can put getApiData in useEffect direct and use it...
You can use useCallBack, useEffect is go to re-render and make mempry leeek issue since every time react render Dashboard its re-create the getAPIData, you can prevent this case by using useCallBack, and you must make sure about dependency, just you need to put what you need...for example:
Ok, I am pulling my hair out.
I am trying to utilize useEffect in my context to populate a list on screen.
useEffect(() => {
Axios.get("http://localhost:8080/getItems").then((response) => {
console.log(response);
setItems(response.data);
});
}, [items]);
This allows me to populate the list each time I add an item to the database. However, it causes an endless loop in Spring.
Now, I can stop the endless loop by changing [items] to []. Yet, when I add to my database it no longer renders the change on the screen.
Does anyone know a workaround to the problem? To be exact, I still want to render an updates to the database without having an endless loop
If you update your database you are not going to receive the changes in your frontend if you don't perform a request to get them.
There are a several mechanims to achieve that:
Pull (every X period of time you perform a request asking for changes)
Push your backend notifies frontend when a new change is performed.
Pull mechanisn is the one you called endless loop.
Push mechanins you need to use websockets for example or a library like socket.io to perform it.
First you change items array which fires useEffect, after when items are fetched you change it again and useEffect fires again. Thats why you get endless loop.
You can try to do something like this:
const [isLoaded, setIsLoaded] = useState(false);
const pushItem = (item) => { // Place where you add new item.
setItems([...items, item]);
setIsLoaded(false);
};
useEffect(() => {
if (isLoaded) {
return;
}
Axios.get("http://localhost:8080/getItems").then((response) => {
setItems(response.data);
setIsLoaded(true);
});
}, [items, isLoaded]);