state updated in react - javascript

I am using react js. in that array stored in my state I update that array in user action. then I check the sum of this array if it goes beyond one I am showing error. now problem happened that it shows the error still it updates the value
updateValuePercent = key => {
let MixDataTemp = [...this.state.MixData];
let MixData = [...MixDataTemp];
let reduceValue = 0.005 / 4;
MixData.map(data => {
data.mix.map(name => {
if (name.pk === key.pk) {
name.volume = name.volume - 0.005;
} else {
name.volume = name.volume - reduceValue;
}
return name;
});
return data;
});
let nwSum = updatedData
.map(data => data.mix)
.map(data => data.volume)
.reduce((a, b) => a + b);
if (nwSum <= 1) {//condition to update the data
this.setState({
MixData: updatedData,
MixError: ''
});
} else {
this.setState({ MixError: 'values can not go beyond one' });//showing error on page still updating the state
}
}
I know what I did is a mutable I shouldn't do it but at least it shouldn't my state at least m taking immutable copy of the state
EDIT 1:
here is my codepen link

If you really don't want to show the MixData value you have in state, I'd explicitly tell React that it's null (or some value that makes it clear to the user that it cannot be incremented) i.e.:
if (nwSum <= 1) {
this.setState({
MixData: updatedData,
MixError: ''
});
} else {
this.setState({
MixData: null,
MixError: 'values can not go beyond one'
});
}
It looks like your in your else block you are actually only returning a value for MixError in state. Based on your code, I wouldn't expect to see any value for MixData since it's not being copied over. To copy over the previous value of MixData you'd need to use the spread operator i.e.:
this.setState({
...this.state,
MixError: 'values can not go beyond one'
});
Best of luck!

Related

ReactJS: Updating one state variable based on another state variable causes the update variable to not render

I'm a ReactJS notive and while related questions on this topic have been asked, but I couldn't find the answer I'm looking for.
In ReactJS, I have two state variables. When one changes (let's call it A), I want the other (B) to change. My code currently does this correctly; when I drop breakpoints or log to console, B changes correctly when A changes. However, React does not render the updated B until A changes again. What is the cause, and what is the right React pattern to ensure B renders?
Snippets of my code (happy to answer more)
This is my variable A:
const [prompt, setPrompt] = useState(params.prompt);
This is my variable B:
let defaultPromptsResultsArray = [{
isLoading: true,
prompt: params.prompt,
counter: 0,
URIs: [default_uri]
}]
const [promptsResultsArray, setPromptsResultsArray] = useState(defaultPromptsResultsArray);
This is the useEffect that depends on prompt (my state variable A):
useEffect(() => {
// Take 0 for the newest prompt.
const newBackendEventSource = new EventSource(
url,
{withCredentials: false})
console.log('SSE created!');
newBackendEventSource.addEventListener('open', () => {
console.log('SSE opened!');
});
newBackendEventSource.addEventListener('error', (e) => {
console.log('SSE error!');
if (newBackendEventSource.readyState === EventSource.CLOSED) {
// Connection was closed.
console.log('SSE readyState is CLOSED')
}
console.error('Error: ', e);
});
newBackendEventSource.addEventListener('close', (e) => {
console.log('SSE closed!');
const data = JSON.parse(e.data);
console.log("close data: ", data);
newBackendEventSource.close();
});
newBackendEventSource.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log("message data: ", data);
// Use React Updater function to prevent race condition.
// See https://stackoverflow.com/a/26254086/4570472
setPromptsResultsArray((prevPromptsResultsArray) => {
// Since we preprend new results, we need to compute the right index from
// the counter with the equation: length - counter - 1.
// e.g., For counter 2 of a length 3 array, we want index 0.
// e.g., For counter 2 of a length 4 array, we want index 1.
// e.g., For counter 3 of a length 7 array, we want index 4.
// Recall, the counter uses 0-based indexing.
const index = prevPromptsResultsArray.length - data.counter - 1
prevPromptsResultsArray[index] = {
isLoading: false,
prompt: prevPromptsResultsArray[index].prompt,
counter: prevPromptsResultsArray[index].counter,
URIs: [data.uri]}
return prevPromptsResultsArray
});
});
// Add new backend event source to state for persistence.
setBackendEventSources(backendEventSources => [
newBackendEventSource,
...backendEventSources])
}, [prompt]);
This is where my promptsResultsArray is used in the DOM:
{promptsResultsArray.map((promptResults) => {
const promptResultsKey = [promptResults.prompt, promptResults.counter].join("_");
return (
// Add a fragment ( a fake div ) so we can return 2 elements.
<Fragment key={promptResultsKey}>
<p key={`${promptResultsKey}_p1`}>Prompt: {promptResults.prompt}</p>
{/* Creating keys from multiple values: https://stackoverflow.com/a/40425845/4570472*/}
<ImageList cols={1} key={promptResultsKey}>
{promptResults.URIs.map((URI) => (
// Refactor to give each URI its own unique integer ID.
<ImageListItem key={[promptResults.prompt, promptResults.counter, 0].join("_")}>
<img
src={URI}
alt={promptResults.prompt}
style={{height: 260, width: 1034}}
/>
</ImageListItem>
))}
</ImageList>
</Fragment>
)
})}
promptsResultsArray is only updated when prompt is updated. Why? How do I ensure promptsResultsArray is also updated when changed by the SSE?

why my React setstate is not updating immediately?

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.

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

array.forEach works, but not when I nest another inside

I've got two pages I'm working on, and both return an array of objects. When I use the following code, the new results work:
this.adminService.waiversGetAll()
.subscribe((data: Waiver[]) => {
this.waivers = data;
this.waivers.forEach((e) => {
if(e.has_signed === true) {
e.url = `View`
} else {
e.url = `${e.message}`;
}
return e;
});
console.log(this.waivers);
})
}
But when I try to do the same thing with a different array (where I need to update the values of an array nested inside) I don't get updated values:
this.adminService.GetUnsignedWaivers()
.subscribe((data: Player[]) => {
console.log("data",data);
data.forEach(e => {
let record: Object = {};
for(let i = 0; i < e.waivers.length; i++) {
console.log(e.waivers[i].has_signed);
if (e.waivers[i].has_signed === true) {
e.waivers[i].url = e.waivers[i].signatureUrl;
console.log(e.waivers[i].url);
e.waivers[i].message = "View Waiver";
} else {
e.waivers[i].url = e.waivers[i].url;
e.waivers[i].message = e.waivers[i].message;
}
console.log(e.waivers[i].message);
return;
};
return e;
});
this.size = this.players.length;
console.log(this.players);
})
}
When I look at the console.log of e.waivers[i].has_signed, the data is correct, but after that it's not right.
What do I have to do to make this work? I've tried using a for loop inside the foreach, and a bunch of other stuff.
The data supplied to the loop provides info like:
{
buyer: "email#someaddress.edu"
event: "COED A"
field: "Main"
net: null
player: {shirtSize: null, avp_id: 12345678, adult: true, …}
team: null
waivers: [{
email: "someemail#gmail.com",
has_signed: true,
message: "Liability Waiver",
signatureUrl: "https://somelink.pdf",
url: "https://somelink.com/somekeyidentifier"
}
IF the player has signed the waiver, there will be a signatureUrl field and the message should say "View Waiver" instead of the message telling me what type of waiver they will sign. I want the url to be set to signatureUrl if they signed, so I can use it in a table that doesn't like manipulation of data.
A visual of what is returned in my table:
All I get is 1600 records showing the url as though everyone hasn't signed, but when I console.log has_signed in the inner loop, it's showing TRUE for the ones that should show a signatureUrl instead.
Quickly looking at it, you have a return statement within your for loop, which would stop it from running after the first iteration.
First of all drop all the return statements in your code. Next, use map instead of forEach as the former returns you the new manipulated array and the latter is used just for iteration purpose.
Your code within subscribe then becomes:
data.waivers = data.waivers.map((waiver) => {
if (waiver.has_signed) {
// your logic goes here...
waiver.url = waiver.signatureUrl;
waivers.message = "View Waiver";
}
// No else is required as you are just reassigning with same values
});
this.playerDetails = data;
At last bind this modified data in your template.

Categories