why my React setstate is not updating immediately? - javascript

This a my function
onSelectAction = (x, o) => {
var { takeActionsOptions } = this.props.main
console.log(o, "onSelectAction")
var tempAction=_.cloneDeep(takeActionsOptions)
_.keys(tempAction).map(a => {
if(a === x.processCode) {
tempAction[a].map((b)=>{
b.isSelected = b.id === o.id
})
}
else{
tempAction[a].map((b)=>{
b.isSelected = b.id === 0
})
}
})
StoreActions.setState({ takeActionsOptions:tempAction});
this.onClickTakeAction(o, x)
}
where tempAction is changing the property like the i wanted to. But when i m trying update the store... this { takeActionsOptions:tempAction} is not getting updated for the first time. After 2-3 clicks on the desired location this is getting updated. i want to update immediately in the store because there is another function which fetches data from the store and does another operation.
this is my other function which is using the take "takeActionsOptions " from store. so if that function is not updating then this function isnt working properly
onClickTakeAction = (o, x) => {
var { takeActionsOptions=[] } = this.props.main
var selectedAction = takeActionsOptions[x.processCode].find(a => a.isSelected)
if (selectedAction.id === 0) {
hydro.msg.info("Please select an option.")
return;
}
var tempAction=_.cloneDeep(takeActionsOptions)
_.keys(tempAction).map(a => {
tempAction[a].map((b)=>{
b.isSelected = b.id === 0
})
})
this.setState({takeActionsOptions:tempAction})
switch (selectedAction.id) {
case 1:
var userName = somecode.userName;
if (userName.toUpperCase() === x.userName.toUpperCase()) {
Actions.deleteSelectedProcess(x);
}
else {
somecode.info("Not your Process")
}
break;
case 2:
Action.downloadLogs(x);
break;
}
}

var tempAction=_.cloneDeep(takeActionsOptions)
What the cloneDeep function is doing here? If it does any API calling/Getting data from the server, you need to wait for a moment to get the data. Meanwhile, you can disable the button and show some loaders for interactivity.
If you're using the loadash to deep copy the object, up to my knowledge loadash functions, takes a long time to complete based on the CPU or object you are trying to copy. So try to wait for a minute and check whether it's updating or not. If it is updating, then you should disable the button until then.

Related

Simplify forEach in forEach React

I have a function where I have to return for each "subcontractor" its response for each selection criteria.
Subcontractor object contains a selectionCriteria object. selectionCriteria object contains an array of data for each selectionCriteria a user has responded to.
Each array item is an object, that contains files, id, request (object that contains info about selection criteria user is responding to), response (contains value of the response).
Here is an example of how a subcontractor looks:
This is the function I come up with, but it's quite complex:
const { subcontractors } = useLoaderData<typeof loader>();
const { t } = useTranslation();
const submittedSubcontractors = subcontractors.filter(
(s) => s.status === 'submitted'
);
const subcontractorsResponsesToSelectionCriteria: Array<ISubcontractor> = [];
let providedAnswersResponded: boolean | null = null;
let providedAnswersFiles: Array<IFile> | [] = [];
let providedAnswersRequiresFiles: boolean | null = null;
submittedSubcontractors.forEach((u) => {
u.selectionCriteria.forEach((c) => {
if (c.request.id === criteriaId) {
if (c.response && 'answer' in c.response) {
if (typeof c.response.answer === 'boolean') {
providedAnswersResponded = c.response.answer;
} else {
providedAnswersResponded = null;
}
} else {
providedAnswersResponded = null;
}
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u as ISubcontractor);
}
});
});
How could I simplify this code by using .reduce() method, or maybe even better ideas?
You should start working on reducing the level of nesting in your if/else like so:
function getProvidedAnswersResponded(response: any) {
if (response && ('answer' in response) && (typeof response.answer === 'boolean')) {
return response.answer;
}
return null;
}
submittedSubcontractors.forEach(u => {
u.selectionCriteria.forEach(c => {
if (c.request.id !== criteriaId) {
return;
}
providedAnswersResponded = getProvidedAnswersResponded(c.response);
providedAnswersFiles = c.files;
providedAnswersRequiresFiles = c.request.are_files_required;
subcontractorsResponsesToSelectionCriteria.push(u);
});
});
The strategy followed was basically to invert the special cases (such as c.requet.id === criteriaId) and exit the function immediately.
Also, extracting the "provided answer responded" function seems atomic enough to move it to a separate block, giving it more verbosity about what that specific code block is doing.

React Child Component Is Not Rerendering When Props Are Updated

My parent component takes input from a form and the state changes when the value goes out of focus via onBlur.
useEffect(() => {
let duplicate = false;
const findHierarchy = () => {
duplicationSearchParam
.filter(
(object, index) =>
index ===
duplicationSearchParam.findIndex(
(obj) => JSON.stringify(obj.name) === JSON.stringify(object.name)
)
)
.map((element) => {
DuplicateChecker(element.name).then((data) => {
if (data.status > 200) {
element.hierarchy = [];
} else {
element.hierarchy = data;
}
});
if (duplicate) {
} else {
duplicate = element?.hierarchy?.length !== 0;
}
});
return duplicate;
};
let dupe = findHierarchy();
if (dupe) {
setConfirmationProps({
retrievedData: formData,
duplicate: true,
responseHierarchy: [...duplicationSearchParam],
});
} else {
setConfirmationProps({
retrievedData: formData,
duplicate: false,
responseHierarchy: [],
});
}
}, [duplicationSearchParam]);
I have a child component also uses a useeffect hook to check for any state changes of the confirmationProps prop.
the issue is that the event gets triggered onblur, and if the user clicks on the next button. this function gets processes
const next = (data) => {
if (inProgress === true) {
return;
}
inProgress = true;
let countryLabels = [];
formData.addresses?.map((address) => {
fetch(`/api/ref/country/${address?.country}`)
.then((data) => {
countryLabels.push(data.label);
return countryLabels;
})
.then((countries) => {
let clean = MapCleanse(data, countries);
fetch("/api/v1/organization/cleanse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(clean),
})
.then((data) => {
if (data.status > 200) {
console.log(data.message);
message.error(getErrorCode(data.message.toString()));
} else {
Promise.all([confirmationProps, duplicationSearchParam]).then(
(values) => {
console.log(values);
console.log(data);
setCleansed(data);
**setCurrent(current + 1);**
inProgress = false;
}
);
}
})
.catch((err) => {
console.log(err);
inProgress = false;
});
})
.catch((err) => {
console.log(err);
inProgress = false;
});
});
console.log(confirmationProps);
};
The important part in the above code snippet is the setCurrent(current + 1) as this is what directs our code to render the child component
in the child component, i have a use effect hook that is watching [props.duplicateData.responseHierarchy]
I do output the values of props.duplicateData.responsehierarchy to the console to see if the updated information gets passed to the child component and it does. the values are present.
I have a conditional render statement that looks like this
{cleansedTree?.length > 0 || treeDuplicate ? (...)}
so although the data is present and is processed and massaged in the child component. it still will not re render or display properly. unless the user goes back to the previous screen and proceeds to the next screen again... which forces a re-render of the child component.
I have boiled it down and am assuming that the conditional rendering of the HTML is to blame. Or maybe when the promise resolves and the state gets set for the confirmation props that the data somehow gets lost or the useefect doesn't pick it up.
I have tried the useefect dependency array to contain the props object itself and other properties that arent directly related
UPDATE: this is a code snippet of the processing that gets done in the childs useeffect
useEffect(() => {
console.log(props.duplicate);
console.log(props.duplicateData);
console.log(props.confirmationProps);
let newArray = props.duplicateData.filter((value) => value);
let duplicateCheck = newArray.map((checker) =>
checker?.hierarchy?.find((Qstring) =>
Qstring?.highlightedId?.includes(UUIDToString(props?.rawEdit?.id))
)
);
duplicateCheck = duplicateCheck.filter((value) => value);
console.log(newArray, "new array");
console.log(duplicateCheck, "duplicate check");
if (newArray?.length > 0 && duplicateCheck?.length === 0) {
let list = [];
newArray.map((dupeData) => {
if (dupeData !== []) {
let clean = dupeData.hierarchy?.filter(
(hierarchy) => !hierarchy.queryString
);
let queryParam = dupeData.hierarchy?.filter(
(hierarchy) => hierarchy.queryString
);
setSelectedKeys([queryParam?.[0]?.highlightedId]);
let treeNode = {};
if (clean?.length > 0) {
console.log("clean", clean);
Object.keys(clean).map(function (key) {
treeNode = buildDuplicate(clean[key]);
list.push(treeNode);
return list;
});
setCleansedTree([...list]);
setTreeDuplicate(true);
} else {
setTreeDuplicate(false);
}
}
});
}
}, [props.duplicateData.responseHierarchy]);
This is a decently complex bit of code to noodle through, but you did say that **setCurrent(current + 1);** is quite important. This pattern isn't effectively handling state the way you think it is...
setCurrent(prevCurrent => prevCurrent + 1)
if you did this
(count === 3)
setCount(count + 1) 4
setCount(count + 1) 4
setCount(count + 1) 4
You'd think you'd be manipulating count 3 times, but you wouldn't.
Not saying this is your answer, but this is a quick test to see if anything changes.
The issue with this problem was that the state was getting set before the promise was resolved. to solve this issue I added a promise.all function inside of my map and then proceeded to set the state.
What was confusing me was that in the console it was displaying the data as it should. but in fact, as I learned, the console isn't as reliable as you think. if someone runs into a similar issue make sure to console the object by getting the keys. this will return the true state of the object, and solve a lot of headache

can't use state value right after setState()

Currently i'm doing a quiz composed by multiple categories that can be chosen by the user and i wanna check if the user responded to all questions. For doing that, i compared the number of questions he answered with the number of questions gived by the api response. The problem is that i have an "submit answers" button at the end of the last question, with that onClick function:
const sendAnswers = (e, currentQuiz) => {
setQuizzes({...quizzes, [currentQuiz]:answers});
setAnswers([])
var answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in quizzes){
if(Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions=false;
}
}
});
if(answeredToAllQuestions === false){
setAlertTrigger(1);
}
else{
setNumber(number+1);
}
}
in that function i use setState on this line: setQuizzes({...quizzes, [currentQuiz]:answers}); to upload the answers he checked on the last question before checking if he answered to all questions. The problem is that state of quizzes is not updated imediatly and it s not seen by the if condition.
I really don't know how am i supposed to update the state right after setting it because, as i know, react useState updates the state at the next re-render and that causes trouble to me..
Considering that quizzes will be equal to {...quizzes, [currentQuiz]:answers} (after setQuizzes will set it), there is no reason to use quizzes in if condition. Replace it with a local var and problem will be solved.
const sendAnswers = (e, currentQuiz) => {
let futureValueOfQuizzes = {...quizzes, [currentQuiz]:answers}
setQuizzes(futureValueOfQuizzes);
setAnswers([])
var answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in futureValueOfQuizzes){
if(Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions=false;
}
}
});
if(answeredToAllQuestions === false){
setAlertTrigger(1);
}
else{
setNumber(number+1);
}
}
I would like to take this opportunity to say that these type of problems appear when you use React state for your BI logic. Don't do that! Much better use a local var defined in components body:
const Component = () => {
const [myVar , setMyVar] = useState();
let myVar = 0;
...
}
If myVar is used only for BI logic, use the second initialization, never the first!
Of course sometimes you need a var that is in BI logic and in render (so the state is the only way). In that case set the state properly but for script logic use a local var.
You have to either combine the useState hook with the useEffect or update your sendAnswers method to perform your control flow through an intermediary variable:
Using a temporary variable where next state is stored:
const sendAnswers = (e, currentQuiz) => {
const newQuizzes = {...quizzes, [currentQuiz]:answers};
let answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in newQuizzes){
if (Object.keys(quiz.questions).length !== Object.keys(newQuizzes[quiz.category]).length){
answeredToAllQuestions = false;
}
}
});
setQuizzes(newQuizzes);
setAnswers([]);
if (answeredToAllQuestions === false) {
setAlertTrigger(1);
} else {
setNumber(number+1);
}
}
Using the useEffect hook:
const sendAnswers = (e, currentQuiz) => {
setQuizzes({...quizzes, [currentQuiz]:answers});
setAnswers([]);
}
useEffect(() => {
let answeredToAllQuestions = true
DataState.map(function (quiz) {
if(quiz.category in quizzes){
if (Object.keys(quiz.questions).length !== Object.keys(quizzes[quiz.category]).length){
answeredToAllQuestions = false;
}
}
});
if (answeredToAllQuestions === false) {
setAlertTrigger(1);
} else {
setNumber(number+1);
}
}, [quizzes]);

How can I check if there's an object with a same key and update with a same key and different properties in array(javascript)?

Hi I'm working on a react project using Socket.io
On client side, it updates when an user click a different button with an Object such as
var actionArray, setActionArray
[actionArray, setActionArray] = React.useState({})
setActionArray({key: "rgb(211,40,21)", gesture: "🤘"})
As the user click a different button, only the gesture property changes.
On server side, several users will update their gesture simultaneously, and the color is used as key value to recognize different users. I want to accumulate the objects with different key in this 'colorArray' array, and update only the 'gesture' property when the user update their 'gesture' value with the same key property.
var colorArray = [];
socket.on("action", data => {
notifySubscribers(data);
console.log(data);
if (data.actionArray) {
colorArray.push({
key: data.actionArray.key,
id: id,
gesture: data.actionArray.gesture
});
subscribers.forEach(socket =>
socket.emit("colorArray", { colorArray: colorArray })
);
console.log(colorArray);
}
With the code above, it keeps adding new objects with same key value, but how can I update the object with same key with different 'gesture' property?
Here's some of the methods I tried, and didn't work.
//1
colorArray = colorArray.filter(item => item.key !== data.actionArray.key);
//2
let updatedItem = colorArray.find(element => {
return element.key === colorArray.key;
});
updatedItem.gesture = data.actionArray.gesture;
You were getting close, try this.
if (data.actionArray) {
let matchIndex = colorArray.findIndex(element => {
return element.key === data.actionArray.key
});
updatedItem = {
...data.actionArray,
id: id,
};
if (matchIndex > -1) {
colorArray[matchIndex] = updatedItem;
} else {
colorArray.push(updatedItem);
}
subscribers.forEach(socket =>
socket.emit("colorArray", { colorArray: colorArray })
);
console.log(colorArray);
}

Why is it that My Array is Undefined after Pushing an Element from an Observable

So basically, I have a web application that retrieves data from Firebase using rxjs observables.
here's my code,
initializeItems(){
this.travelList$ = this.plsdala.getTravelList()
.snapshotChanges()
.map(
changes => {
return changes.map(c=>({
key: c.payload.key, ...c.payload.val()
})).slice().reverse();//to reverse order
})
this.travelList$.subscribe(res => {
for(let i=0;i<res.length;i++){
this.ListOfitems.push (res[i].toAddress);
}
})
}
this is called from the constructor. problem here is that i cannot check if it is push successfully and if try to print in console , it wont print. why?
the element pushed is needed for filtering. heres is the code for filtter. but when i print the this.ListOfitems in console it is undefined and im wondering unto why? when the elements are initialized first
getItems(ev: any) {
console.log("awdaw");
console.log(this.ListOfitems);
if (this.ListOfitems.length>1){
console.log("otin");
let val = ev.target.value;
if (val && val.trim() != '') {
this.ListOfitems = this.ListOfitems.filter((ListOfitems) => {
return (ListOfitems.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
}
when you are declaring list of items if you want to push into the array you need to declare it empty first like this:
ListOfitems: string[] = [];

Categories