React State not updating immediatly - javascript

I am trying to retrieve data from api and show it in my array but when i setting it up in my useState, it is not updating else it is updating after the 1st render means on the 2nd render and on the first render it is returning the previous state.
Here is my code:
const getMembers = async (id) => {
const groupMembers = await getGroupMembers(id);
setLoading(true);
setMembersData(groupMembers);
if(membersData){
const result = usersList.filter(e => !membersData.some(a => a.userId === e.id));
setRemainingUsers(result);
console.log(result)
} else {
setUsersList(usersList)
}
setLoading(false);
};
let options = remainingUsers.map(function(user){
return { value: user.id, label: user.username}
})
What i want is to get remaining users value on the first time function is called and it is updating after the first render. I tried using useEffect but it didn't help. I also search on the google regarding this but almost all solutions are to use useEffect. Here is how i use the useEffect hook.
useEffect(() => {console.log(groupId, group, remainingUsers)}, [ groupId, group, remainingUsers ]);

If(memebersData) {}
if(groupmembers){
const result = usersList.filter(e => !groupmembers.some(a => a.userId === e.id));
}```

Related

I have an array of different IDs and I want to fetch data from each IDs . Is it possible?

I have an array of mongoDB ids.
const pId =  ['62b3968ad7cc2315f39450f3', '62b37f9b99b66e7287de2d44']
I used forEach to seperate the IDs like :
pId.forEach((item)=>{
console.log(item)
})
but I have a database(products) from where I want to fetch data from. So I tried
const [product, setProduct] = useState([{}]);
useEffect(() => {
pId?.forEach((item) => {
const getProduct = async () => {
try {
const res = await userRequest.get("/products/find/" + item)
setProduct(res.data)
} catch (err) {
console.log(err)
}
}
getProduct()
})
}, [pId])
I used useState[{}] because I want to collect the data in an array of objects.
I used useState[{}] because I want to collect the data in an array of objects.
Your code isn't collecting objects into an array. It's setting each result of the query as the single state item (overwriting previous ones).
If you want to get all of them as an array, build an array; one way to do that is map with the map callback providing a promise of each element, then use Promise.all to wait for all of those promises to settle:
// The usual advice is to use plurals for arrays, not singulars ("products", not "product")
const [products, setProducts] = useState([]); // Start with an empty array
useEffect(() => {
if (pId) {
Promise.all(pId.map((item) => userRequest.get("/products/find/" + item)))
.then((products) => setProducts(products))
.catch((error) => console.log(error));
}
}, [pId]);
Note that if pId changes while one or more userRequest.get calls are still outstanding, you'll get into a race condition. If userRequest.get provides a way to cancel in-flight calls (like fetch does), you'll want to use that to cancel the in-flight calls using a cleanup callback in the useEffect. For example, if userRequest.get accepted an AbortSignal instance (like the built-in fetch does), it would look like this:
const [products, setProducts] = useState([]);
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
if (pId) {
Promise.all(pId.map((item) => userRequest.get("/products/find/" + item, { signal })))
.then((products) => setProducts(products))
.catch((error) => {
if (!signal.aborted) {
console.log(error);
}
});
}
return () => {
controller.abort();
};
}, [pId]);
Again, that's conceptual; userRequest.get may not accept an AbortSignal, or may accept it differently; the goal there is to show how to cancel a previous request using a useEffect cleanup callback.
You can map through the ids and create a promise for each, then use Promise.all() and at last set the products state:
import React, {
useState,
useEffect
} from 'react'
const Example = () => {
const [products, setProducts] = useState([]);
const ids = ['62b3968ad7cc2315f39450f3', '62b37f9b99b66e7287de2d44']
useEffect(() => {
if(ids) Promise.all(ids.map(id => userRequest.get("/products/find/" + id).then(r => r.data))).then(results => setProducts(results))
}, [ids])
}
I renamed some of the variables for clarity to the future visitors. (pId to ids and product to products).
You're overwriting your product array with an individual result from each request. A simple solution would be to append to the array instead:
setProduct(product => [...product, res.data]); // take the old array and append the new item
As T.J. Crowder rightly suggested in the comments, you would keep appending to the initial product state when using the simple setter, so you need to use callback form which receives the current state as a parameter and returns the update.
I suggest you rename that particular state to products/setProducts to make clear it's an array.
Not directly related to the question, but bear in mind that firing a huge number of individual requests may cause performance degradation on the client and backend; there are plenty of options to deal with that, so I am not going into more detail here.
yes, it's possible. just change your code a bit:
const [product, setProduct] = useState([]); // empty array is enough for initialzing
useEffect(() => {
async function doSomethingAsync() {
if(!pId) return;
let newArray = [];
for(let item of pId) {
try {
const res = await userRequest.get("/products/find/" + item)
newArray.push(res.data); // push data to temporary array
} catch (err) {
console.log(err)
}
}
// set new state once
setProduct(newArray);
}
doSomethingAsync();
}, [pId])

Firestore data doesn't show up

In my custom hook, I am getting all the images in firestore collection. That was working fine UNTIL I have tried to get also metadata's of images. When I added the getMetaData function, I can't set the data to state even the datas seem fine in an array.
See the code please.
import { useState, useEffect } from "react";
import { firebaseFireStore } from "../firebase/config";
import { storage } from "../firebase/config";
import { ref, getMetadata } from "firebase/storage";
const useFirestore = (collection) => {
const [docs, setDocs] = useState([]);
useEffect(() => {
const unsub = firebaseFireStore
.collection(collection)
.onSnapshot((snap) => {
let documents = [];
snap.forEach((doc) => {
const forestRef = ref(storage, doc.data().url);
getMetadata(forestRef)
.then((metadata) => {
documents.push({ ...doc.data(), metadata, id: doc.id });
return documents;
})
.catch((error) => {
console.log(error);
});
});
setDocs(documents);
console.log(docs); // [] EMPTY ARRAY, PROBLEM IS HERE.
console.log(documents); // [] length:13 data is complete inside.
});
return () => unsub();
// this is a cleanup function that react will run when
// a component using the hook unmounts
}, []);
return { docs };
};
export default useFirestore;
So, even if documents array has the data with metadata in it, it can't be set to state of docs.
Any help appreciated.
You can't log the docs state right after enqueueing a state update since React asynchronously processes the state updates. But this isn't exactly your issue or what you are actually trying to solve. The code is mapping over the snapshot and enqueueing an asynchronous request to retrieve metadata and asynchronously mutating the documents array that was updated in React a long* time ago. The console.log(documents) is just exposing this mutation later when the browser is processing the log buffer.
To resolve I suggest declaring the snapshot.forEach callback async so it can wait for the getMetadata call to resolve. Use a functional state update to shallow merge each document as it resolves with metadata.
Example:
useEffect(() => {
const unsubscribe = firebaseFireStore
.collection(collection)
.onSnapshot((snap) => {
snap.forEach(async (doc) => {
try {
const forestRef = ref(storage, doc.data().url);
const metadata = await getMetadata(forestRef);
setDocs(documents => [
...documents,
{ ...doc.data(), metadata, id: doc.id }
]);
} catch(error) {
console.log(error);
};
});
});
return unsubscribe;
}, []);
* "Long time ago" -> some previous render cycle.

Update a Table with React Hooks when a Row is Added, Deleted and Modified? [Issue: Gets Before Post and Delete]

I'm using Axios to get, put, and delete values from our database and have them displayed in a table; however, I need to refresh the page to see my changes. To find answers, I've visited these posts: How to update the page after call Axios Successful ? React, refresh table after action in react, and How can I use React Hooks to refresh a table when a row is deleted or added?
Unfortunately, I am still stuck and unsure how to dynamically update table rows upon response updates.
Update: I have noticed that the getValues function runs prior to the post and delete methods, which is why it is currently showing the previous values before the methods execute.
Axios.js - Where I am using get and delete methods. They work as I've had responses printed on the console.
import axios from "axios";
const getValues = async () => {
const values = await axios
.get("https://pokeapi.co/api/v2/type/")
.then((response) => {
return response.data;
})
.catch(function (error) {
console.log(error);
});
return values;
};
const postValues = (values) => {
axios
.post("https://pokeapi.co/api/v2/type/")
.then((response) => {
console.log("Post Values: ", response.data);
return response.data;
});
};
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete(`https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
export { getValues, postValues, deleteValues }
ValuesTable.js - Where the delete method executes
import Axios from "./Axios";
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [data]);
return (
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button
onClick={() =>
Axios.deleteValues(values.id);
}
/>
})};
)
Form.js - Where the post method executes
if (values.id === 0) {
Axios.postValues(values);
} else {
Axios.putValues(values, values.id);
}
UseState setData(result.data) loads all the existing values in the database.
Method deleteValues deletes a value in an array.
Method postValues adds a value into the database.
Well, you don't what to unconditionally call setData within an useEffect hook with data as a dependency as this will cause an infinite loop (render looping) to occur.
Since the getValues utility already unpacks the response.data value there is likely no need to do it again in your UI. Also, remove the data dependency.
useEffect(() => {
Axios.getValues()
.then((result) => {
setData(result.results);
});
}, []);
For the deleteValues utility, if console.log("Delete Values: ", response); is showing the correct values than I think you need to return this value from deleteValues.
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete("https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
return response; // <-- new data values
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
Then in ValuesTable you need to update your data state with the new deleted values.
{data.map((values) => {
...
<Button
onClick={() => {
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};
Update
Ok, since the deleteValues utility doesn't return the updated data from the backend you will need to maintain your local state manually. I suggest doing this work in a callback handler. Upon successful deletion, update the local state.
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, []);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
setData(data => data.filter((item) => item.id !== id));
} catch(err) {
// whatever you want to do with error
}
};
return (
...
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button onClick={deleteHandler(values.id)}>
Delete
</Button>
})};
...
)
Note that I've written deleteHandler to be a curried function so you don't need an anonymous callback function for the button's onClick handler. It encloses the current id in an "instance" of the callback.
Update 2
If you are making a lot of different changes to your data in the backend it may just be easier to use a "fetch" state trigger to just refetch ("get") your data after each backend update. Anytime you make a call to update data in your DB, upon success trigger the fetch/refetch via a useEffect hook.
const [data, setData] = React.useState();
const [fetchData, setFetchData] = useState(true);
const triggerDataFetch = () => setFetchData(t => !t);
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [fetchData]);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
triggerDataFetch(); // <-- trigger refetch
} catch(err) {
// whatever you want to do with error
}
};
I think you wrong in here :
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data); // result has no data property
});
}, [data]);
Please try change to this
useEffect(() => {
Axios.getValues().then((result) => {
console.log("RESULT",result); // check the actually response from API
setData(result.results); // the response of data is called results
});
}, [data]);
import axios from "axios"; const getValues = async () => { const values = await axios .get("https://pokeapi.co/api/v2/type/")
.then((response) => { return response.data; }) .catch(function (error)
{ console.log(error); }); return values; };
I don't know why but what are you trying to achieve with this. You should either use async/await clause or then clause but you are using both atleast have some good practice of coding first.
Second I think you should use async await inside try catch and remove then/catch phrases to make your code more understandable, then if you store your result inside values then simply return values.data and your problem might be resolved.
Since the deleteValues function deletes a specific object from the array on the server-side, I have decided to filter the list of objects in the array in order to remove the matching id to reflect on the front end. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
This is how I approached it.
{data.map((values) => {
...
<Button
onClick={() => {
setData(data.filter((item) => item.id !== values.id)); // <- Filter
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};

React-hook setState does not work as expected

I have the following code:
const classes = useStyles();
const [data1, setData1] = useState([]);
const [searchedString, setSearchString] = useState("");
console.log(data1);
const fetchDataHandler = async () => {
setData1([]);
axios
.get(`http://localhost:5000/select?articul=${searchedString}`)
.then((response) => {
dataStruction(response.data);
})
.catch((err) => {
console.log(err);
});
};
const dataStruction = (data) => {
data.map((element1) => {
if (element1.secondaryArt.startsWith("30")) {
return setData1([...data1, { ...element1, level: 1 }]);
}
});
};
const onChangeSearchText = (event) => {
setSearchString(event.target.value);
};
I want whenever I call fetchDataHandler to be able to set data1 to empty array. Now it is working as that results are sticking every time I call fetchDataHandler. How can I do it?
Problem:
Your Asynchronous handler dataStruction closes over data1 before a new render is triggered at setData1([]); (at the top of your async function).
This happens because React state updates are batched and asynchronous.
Simple Solution:
If you get rid of (delete the line) setData1([]); and change setData1([...data1, { ...element1, level: 1 }]); to setData1([{ ...element1, level: 1 }]); then you will get an array with a new element in it without preserving the "old" elements.
Alternative Solution:
You can also wrap your state updates into functions like so:
Turn this: setState("foo")
Into this: setState((state, props) => "foo")
The second form (passing a function instead directly a state) ensures that the correct state is referenced. So in your case the second state update would reference the updated state.

React issue with lodash and setInterval

I have an issue with using Lodash + setInterval.
What I want to do:
Retrieve randomly one element of my object every 3 seconds
this is my object:
const [table, setTable]= useState ([]);
so I start with that:
const result = _.sample(table);
console.log(result);
console give => Object { label: "Figue", labelEn: "fig" }
But if a add :
const result = _.sample(table);
console.log(result.label);
console give => TypeError: result is undefined
Beside that I tried to add setInterval and also try with useEffect but or code crash or console give me two numbers every 3 second => 2,6,2,6 ......
Ciao, supposing that table object is correclty filled, you could use lodash and setInterval to get random object of table using useEffect and useRef hooks like:
const interval = useRef(null);
useEffect(() => {
if (!interval.current) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
Then to clean setInterval when component will be unmount you could use another useEffect in this way:
useEffect(() => {
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
EDIT
After your explanation, I found the cause of your problem. Before start setInterval you have to wait that table was filled with values (otherwise you get an error).
To solve that it's just necessary to put another condition on first useEffect (table.length > 0), and load data on second useEffect.
So the first useEffect becomes:
const interval = useRef(null);
useEffect(() => {
if (!interval.current && table.length > 0) {
interval.current = setInterval(() => {
const result = _.sample(table);
console.log(result.label);
}, 3000);
}
}, [table]);
And the second one:
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
return () => {
clearInterval(interval.current);
interval.current = null;
};
}, []);
Here the codesandbox updated.
The problem is that you access table before it is loaded.
You should either provide an initial value to that allows a successful do all your operations:
// use an array that has at least one element and a label as initial table value
const [table, setTable] = useState([{label: "default label"}]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
console.log(result.label);
// ...
Or use an if or something alike to handle multiple scenarios:
// use an empty array as initial table value
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
// account for the initial empty table value by checking the result
// of _.sample which is `undefined` for empty arrays
if (result) {
console.log(result.label);
} else {
console.log("do something else");
}
// ...
If you fetch your data asynchronously you must think about what you want to use while the data is being fetched. The minimal thing to do is tell React to not render the component while the data is missing (being fetch).
const [table, setTable] = useState([]);
useEffect(() => {
jsonbin.get("/b/5f3d58e44d93991036184474/5")
.then(({data}) => setTable(data));
});
const result = _.sample(table);
if (!result) return null; // <- don't render if there is no result
console.log(result.label);
// ...

Categories

Resources