Using context in React while using useEffect - reactjs

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]);

Related

How do I asynchronously update a variable from a paginated API using React hooks?

I'm currently trying to fetch all of the properties for an object from an API, and display them in a table. The API will return up to 10 results at a time, and will return a value nextPageToken in the response body if there are more results to be fetched. My goal is to fetch the first 10 results, immediately display them in the table, and add to the table as I continue to hit the API. This was my first attempt at a solution:
const getProperties = async (id) => {
const properties = await Api.getProperties(id);
setProperties(properties.properties);
if (properties.nextPageToken) loadMoreProperties(id, nextPageToken);
};
const loadMoreProperties = async (id, nextPageToken) => {
const properties = await Api.getProperties(id, nextPageToken);
setProperties(prevProperties => {return [...prevProperties, properties.properties]});
if (properties.nextPageToken) loadMoreProperties(id, properties.nextPageToken);
};
(Note that the above is a simplification; in practice, there's more logic in getProperties that doesn't need to be repeated on subsequent calls to the API)
The problem that I'm running into with this solution is that when I'm calling loadMoreProperties, the setProperties call isn't yet finished. How can I enforce that the call to loadMoreProperties only happens after setting the previous set of properties? Is there an overall better pattern that I can follow to solve this problem?
You can use useEffect to trigger the page loads as a reaction to a completed state change:
const [page, setPage] = useState(); // will be {properties, nextPageToken}
// load first page whenever the id changes
useEffect(() => {
Api.getProperties(id)
.then(page => setPage(page)));
}, [id]);
// load next page (if there is one) - but only after the state changes were processed
useEffect(() => {
if (page?.nextPageToken == null) return;
Api.getProperties(id, page.nextPageToken)
.then(page => setPage(page)));
}, [id, page]
);
// this will trigger the re-render with every newly loaded page
useEffect(()=> setProperties(prev => [...(prev || []), page.properties]), [page]);
The first effect will cause an update to the state variable page.
Only after the state change is completed, the second effect will be triggered and initiate the fetch of the second page.
In parallel, the third effect will perform the changes to the state variable properties, that your table component depends on, after each successful page load and page state update, triggering a re-render after each update.
I think you should pass a callback parameter to your "setProperties" method, to make the second call after the value has been updated, like this :
setProperties(properties.properties, () => {
if (properties.nextPageToken)
loadMoreProperties(id, nextPageToken);
);
Hope it can help
My solution involves removing the loadMoreProperties method itself.
While calling the getProperties for the 1st time, you can omit the nextPageToken argument.
getProperties = async(id,nextPageToken) {
var result = await Api.getProperties(id,nextPageToken);
this.setState((state)=>(state.properties.concat(result.properties)), ()=>{
// setState callback
if(result.nextPageToken) {
this.getProperties(id, nextPageToken);
}
});
}

Firebase firestore read operation very high

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)

UseEffect still runs even if fetch data doesnt change

i have this UseEffect() code and even if i pass the 2nd paramenter it just keeps re-rendering even if the data doesn't change, Any idea? hope to get a solution soon (MERN)
FRONTEND
const [qty, setQty] = useState([])
useEffect(() => {
fetch('/QtyCheck',{
headers:{
"Content-Type":"application/json"
}
})
.then(res=>res.json())
.then(qtyValue=>{
setQty(qtyValue)
})
},[qty])enter code here
BACKEND
router.get('/QtyCheck',(req,res)=>{
Post.find({},{qty:1,minT:1,criT:1, code:1,name:1,isDisable:1})
.then(postFind=>{
if(postFind.length===0){
res.json("nil")
}else{
res.json(postFind)
}
}).catch(noPost=>{
res.json("Error",noPost)
})
})
I think you are making an infinite loop here because how you are setup the end of brackets [qty]
render > useEffect > fetchData > [Qty](that mean data change) >re-render >useEfect > so on
the most important part here is the empty brackets []
instead you can make a separate function like :
const fetchdata = () =>
{
fetch('/QtyCheck',{
headers:{
"Content-Type":"application/json"
}
})
.then(res=>res.json())
.then(qtyValue=>{
setQty(qtyValue)
})
}
useEffect(()=>{
fetchdata()
},[])`
Your state is an array. That means even when fetch returns the same content result it will have different reference. React won't look at deep level to compare, it does a shallow, and each array has different reference, hence multiples fetches. To solve you could pass to dependency array a stringified version [JSON.stringify(qty)] or do some conditional deep comparasion at your hook before sending the next request.
Regardless, it seems you also need an open channel between back and front, so everytime there is an update on back it sends back to front, since you say it changes from time to time at backend regardless frontend interaction. If that's the case you can use libs like socket.io to create this channel between back and front.

Infinite Loop with useEffect - ReactJS

I have a problem when using the useEffect hook, it is generating an infinite loop.
I have a list that is loaded as soon as the page is assembled and should also be updated when a new record is found in "developers" state.
See the code:
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = async () => {
await api.get('/developers').then(response => {
setDevelopers(response.data);
});
};
// This way, the loop does not happen
useEffect(() => {
getDevelopers();
}, []);
// This way, infinte loop
useEffect(() => {
getDevelopers();
}, [developers]);
console.log(developers)
If I remove the developer dependency on the second parameter of useEffect, the loop does not happen, however, the list is not updated when a new record is found. If I insert "developers" in the second parameter of useEffect, the list is updated automatically, however, it goes into an infinite loop.
What am I doing wrong?
complete code (with component): https://gist.github.com/fredarend/c571d2b2fd88c734997a757bac6ab766
Print:
The dependencies for useEffect use reference equality, not deep equality. (If you need deep equality comparison for some reason, take a look at use-deep-compare-effect.)
The API call always returns a new array object, so its reference/identity is not the same as it was earlier, triggering useEffect to fire the effect again, etc.
Given that nothing else ever calls setDevelopers, i.e. there's no way for developers to change unless it was from the API call triggered by the effect, there's really no actual need to have developers as a dependency to useEffect; you can just have an empty array as deps: useEffect(() => ..., []). The effect will only be called exactly once.
EDIT: Following the comment clarification,
I register a developer in the form on the left [...] I would like the list to be updated as soon as a new dev is registered.
This is one way to do things:
The idea here is that developers is only ever automatically loaded on component mount. When the user adds a new developer via the AddDeveloperForm, we opportunistically update the local developers state while we're posting the new developer to the backend. Whether or not posting fails, we reload the list from the backend to ensure we have the freshest real state.
const DevList: React.FC = () => {
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = useCallback(async () => {
await api.get("/developers").then((response) => {
setDevelopers(response.data);
});
}, [setDevelopers]);
useEffect(() => {
getDevelopers();
}, [getDevelopers]);
const onAddDeveloper = useCallback(
async (newDeveloper) => {
const newDevelopers = developers.concat([newDeveloper]);
setDevelopers(newDevelopers);
try {
await postNewDeveloperToAPI(newDeveloper); // TODO: Implement me
} catch (e) {
alert("Oops, failed posting developer information...");
}
getDevelopers();
},
[developers],
);
return (
<>
<AddDeveloperForm onAddDeveloper={onAddDeveloper} />
<DeveloperList developers={developers} />
</>
);
};
The problem is that your getDevelopers function, calls your setDevelopers function, which updates your developers variable. When your developers variable is updated, it triggers the useEffect function
useEffect(() => {
getDevelopers();
}, [developers]);
because developers is one of the dependencies passed to it and the process starts over.
Every time a variable within the array, which is passed as the second argument to useEffect, gets updated, the useEffect function gets triggered
Use an empty array [] in the second parameter of the useEffect.
This causes the code inside to run only on mount of the parent component.
useEffect(() => {
getDevelopers();
}, []);

useEffect clears, then refills my react hook

I have two used state arrays were going to call them queries, and messages.
const [queries, setQueries] = useState([]);
const [messages, setMessages] = useState([]);
I have it so when users type a search query it will add the query to a list of queries on screen. When the user deletes one of those queries or adds a new query, my use state will read the last query on the list and run a fetch request all within the useEffect.
useEffect(() => {
if (queries.length > 0) {
const [x] = queries.slice(-1);
axios
.get(`FETCH REQUEST`)
.then((res) => {
setMessages(res.data.messages);
})
.catch((err) => {
console.log(err);
});
} else {
setMessages([]); // <-- this right here is my problem!
}
}, [queries]);
The problem is everything works as it should until the there are no items in the array. It will briefly return an empty message array, then fetch request the last deleted item anyway somehow. Form what I can conclude this is where the problem is located, but I do not understand the why or how despite trying to force my result in various ways. Thank you for any help in advance!
The setState function works asynchronously, if required, would join multiple requests without any specific order to optimize the app performance. This could trigger useEffect multiple times and set your message state incorrectly. Instead, you can set the message state using a callback into the setQueries call.
function cbAddOrDeleteQuery() {
...
setQueries(_ => {
// newVal represents the updated queries
if (newVal.length > 0) {
setMessage(res.data.messages);
} else {
setMessage([]);
}
return newVal;
});
}

Resources