setState not updating state with multiple varaibles - reactjs

I am working on one reactjs project. where the user needs to pick two different dates starting date and end date. When a user picks first date i need to update the state to check whether the starting date should be less than closing date ,But the problem i am facing is setState not updating state immediately. I am facing this problem during update the store timings .How can i fix this problem.
const [storeTimings, setStoreTimings] = useState({
startTime: "",
endTime: "",
startTime2: "",
endTime2: "",
});
const isValidTime = (key, value) => {
setStoreTimings({ ...storeTimings, [key]: value });
console.log({ key, value });
};
<DatePicker
onChange={(date) => {
isValidTime("startTime", date);
}}
/>

State setters can also take a function to update the state. This callback is given the previous state which is what you want.
You should use an updater function instead because using storeTimings directly is not guaranteed to reference the state of your component when you want to update it. Using this function to get the previous state directly before the update allows you to always use the latest state:
const [storeTimings, setStoreTimings] = useState({
startTime: "",
endTime: "",
startTime2: "",
endTime2: "",
});
const isValidTime = (key, value) => {
// provide updater function
setStoreTimings((previous) => ({ ...previous, [key]: value }));
console.log({ key, value });
};
<DatePicker
onChange={(date) => {
isValidTime("startTime", date);
}}
/>
More details about this function argument to state setters can be found in this question.

Related

Modify only one field of the interface with the useState

I defined this interface and hook with an initialization:
interface userInterface {
email:string
name:string
last_name:string
}
const [userData, setUserData] = useState <userInterface> ({
email:"",
name:"",
last_name:"",
})
then if you just wanted to change the name only. How should it be done with setUserData?
That is, I want to leave the email and the last_name as they are but only modify the name
#Jay Lu is correct.
I wanted to note a couple things.
Typescript Interfaces
Interface name should use PascalCasing
Key names should use camelCasing
Prefer utility types where appropriate
[optional] Interface name should be prefaced with a capital "I"
This used to be a convention now more people are moving away from it. Your choice
Interface Update: 1) Naming Conventions
interface UserInterface {
email:string
name:string
lastName:string
}
The next thing we want to do is use a utility type to simplify the verbosity of our code.
Interface Update: 2) Utility Type
We will actually change this interface to a type to easily use the utility type Record.
type UserInterface = Record<"email" | "name" | "lastName", string>;
As for your component, you didn't provide much detail there so I will provide details on setting state.
Functional Component: Updating State
It's very important to establish what variables or data you need to "subscribe" to changes. For instance, if the email and name are static (never changing) there is no need to have them in state which would result in a state variable defaulting to an empty string:
const [userData, setUserData] = useState("");
If that's not the case and you indeed need to update and manage the email, name, and lastName updating state is simple: spread the existing state with the updated value. You do this using the setter function provided in the tuple returned from useState. In this case that's setUserData. The setter function takes a value that is the same type as your default or it can accept a callback function where it provides you the current state value. This is really powerful and really useful for updating a state object variable. In your case we have:
setUserData((previous) => ({...previous, name: "Updated name value"});
What's happening here? The setUserData provides us the "previous" state if we pass it a callback. On the first call of this function "previous" is:
{
email: "",
name: "",
lastName: ""
}
We are taking that value and spreading it over in a new object with the updated value. This is the same as Object.assign. If you spread a key that already exists in the object it will be replaced. After we spread our state object looks like:
{
email: "", // didn't change because we didn't pass a new value
lastName: "", // didn't change because we didn't pass a new value
name: "Updated name value" // changed because we passed a new value
}
Which means, if you wanted to update the email you can by simply doing:
setUserData((previous) => ({...previous, email: "hello#world.com"});
Now your state object will be:
{
email: "hello#world.com",
lastName: "",
name: "Updated name value"
}
And if you call setUserData with a callback again, the previous value with be that object above.
If you want to set it back to the original values you can update the entire state without using the callback. Why? Because we don't need to preserve any values since we want to overwrite it:
setUserData({ email: "", lastName: "", name: ""});
There is a slight improvement to that though. If we decide that at some point we want to "reset to default" we should store the default value in a variable and reuse it. Not 100% necessary but it might be a good update especially if you have a complex component.
Quick Note on the Power of Typescript
If you were to try and update state with a new key that you didn't have defined before let's say "helloWorld" typescript will give you an error because "helloWorld" is not defined in your UserData type.
Hopefully #Jay Lu's answer and some of this info helped. If you provide more details we might be able to offer more guidance.
Simple expample:
setUserData((prev) => ({ ...prev, name: 'Name you want' }));
const { useState } = React;
const DemoComponent = () => {
const [userData, setUserData] = useState({
email: "",
name: "",
last_name: ""
});
const handleBtnOnClick = (name) => {
setUserData((prev) => ({ ...prev, name: name }));
};
return (
<div>
<button
onClick={() => {
handleBtnOnClick("Jay");
}}
>
Jay
</button>
<button
onClick={() => {
handleBtnOnClick("Andy");
}}
>
Andy
</button>
<button
onClick={() => {
handleBtnOnClick("Olivia");
}}
>
Olivia
</button>
<div>{JSON.stringify(userData, null, "\t")}</div>
</div>
);
}
ReactDOM.render(
<DemoComponent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How do I change the state of an array of objects in React?

I'm trying to change the state of an array containing an object whenever something is typed inside of an input. The state of the array I'm trying to change looks like this:
const defaultCV = {
personalInfo: {
firstName: "",
lastName: "",
title: "",
about: "",
},
education: [
{
id: uniqid(),
university: "",
city: "",
degree: "",
subject: "",
from: "",
to: "",
},
],
Specifically, I want to change the state of the 'education' section. My current, non-working code looks like this:
const handleEducationChange = (e) => {
setCV((prevState) => ({
...prevState,
education: [
{
...prevState.education,
[e.target.id]: e.target.value,
},
],
}));
};
When I type in the input and the function is triggered, I get the error "Warning: Each child in a list should have a unique "key" prop." I've been trying to make this work for the past few hours, any help as to what I'm doing wrong would be appreciated.
Are you using the Array.map() method to render a list of components? That is a common cause of that error. For example if you are mapping the education array.
You can fix by using the object id as the key since that is already generated for each object:
defaultCV.education.map(institution => {
return <Component key={institution.id} institution={institution} />
}
You are destructuring an array in to an object that will not work
education: [{ // this is the object you are trying to restructure into
...prevState.education, // this here is an array in your state
[e.target.id]: e.target.value,
}, ],
}));
Suppose you render this input field:
<input id='0' type='text' name='university' value={props.value} />
Your event.target object will include these props:
{id = '0', name = 'university', value = 'some input string'}
When updating the state, you have to first find the array item (object), that has this id prop, then you can change its 'name' property and return the new state object.
This worked for me:
setCV(prevState => {
const eduObjIdx = prevState.education.findIndex(obj => obj.id === +e.target.id)
prevState.education[eduObjIdx][e.target.name] = e.target.value
return {
...prevState,
education: [
...prevState.education.splice(0, eduObjIdx),
prevState.education[eduObjIdx],
...prevState.education.splice(eduObjIdx + 1),
],
}
})
Make sure you send the current state of the input value when rendering the component:
<Component value={state.education[currentId].id} />
where currentId is the id of the university you are rendering.
If you render it mapping an array, don't forget the key (answered at 0), otherwise you'll get the above error message.
This way you don't mutate the whole education array. May not be the best solution though.

How to set state in parent without triggering re-render of children in React?

I have a React web app that is effectively a ton of Questions. These questions need to be validated/laid-out based on their own state values (ie: must be a number in a number field), as well as on the values of each other. A few examples of the more complex 'validation':
Questions A, B, and C might be required to have non-empty values before allowing a 'save' button.
Question B's allowable range of values might be dependent on the value of question A.
Question C might only show if question A is set to 'true'.
You can imagine many other interactions. The app has hundreds of questions - as such, I have their configuration in a JSON object like this:
{ id: 'version', required: true, label: 'Software Version', xs: 3 },
{
id: 'licenseType', label: 'License Type', xs: 2,
select: {
[DICTIONARY.FREEWARE]: DICTIONARY.FREEWARE,
[DICTIONARY.CENTER_LICENSE]: DICTIONARY.CENTER_LICENSE,
[DICTIONARY.ENTERPRISE_LICENSE]: DICTIONARY.ENTERPRISE_LICENSE
}
},
... etc.
I would then turn this object into actual questions using a map in the FormPage component, the parent of all the questions. Given the need to store these interaction in the closest common parent, I store all of the Question values in a formData state variable object and the FormPage looks like so:
function FormPage(props) {
const [formData, setFormData] = useState(BLANK_REQUEST.asSubmitted);
const handleValueChange = (evt, id) => {
setFormData({ ...formData, [id]: evt.target.value})
}
return <div>
{QUESTIONS_CONFIG.map(qConfig => <Question qConfig={qConfig} value={formData[qConfig.id]} handleValueChange={handleValueChange}/>)}
// other stuff too
</div>
}
The Question component is basically just a glorified material UI textField that has it's value set to props.value and it's onChange set to props.handleValueChange. The rest of the qConfig object and Question component is about layout and irrelevant to the question.
The problem with this approach was that every keypress results in the formData object changing... which results in a re-render of the FormPage component... which then results in a complete re-render/rebuild of all my hundreds of Question components. It technically works, but results performance so slow you could watch your characters show up as you type.
To attempt solve this, I modified Question to hold it's own value in it's own state and we no longer pass formData to it... the Question component looking something like this:
function Question(props) {
const { qConfig, valueChangedListener, defaultValue } = props;
const [value, setValue] = useState(props);
useEffect(() => {
if (qConfig.value && typeof defaultValue !== 'undefined') {
setValue(qConfig.value);
}
}, [qConfig.value])
const handleValueChange = (evt, id) => {
setValue(evt.target.value);
valueChangedListener(evt.target.value, id)
}
return <div style={{ maxWidth: '100%' }}>
<TextField
// various other params unrelated...
value={value ? value : ''}
onChange={(evt) => handleValueChange(evt, qConfig.id)}
>
// code to handle 'select' questions.
</TextField>
</div>
}
Notably, now, when it's value changes, it stores it's own value only lets FormPage know it's value was updated so that FormPage can do some multi-question validation.
To finish this off, on the FormPage I added a callback function:
const processValueChange = (value, id) => {
setFormData({ ...formData, [id]: value })
};
and then kept my useEffect that does cross-question validation based on the formData:
useEffect(() => { // validation is actually bigger than this, but this is a good example
let missingArr = requiredFields.filter(requiredID => !formData[requiredID]);
setDisabledReason(missingArr.length ? "Required fields (" + missingArr.join(", ") + ") must be filled out" : '');
}, [formData, requiredFields]);
the return from FormPage had a minor change to this:
return <div>
{questionConfiguration.map(qConfig =>
<Question
qConfig={qConfig}
valueChangedListener={processValueChange}
/>
</ div>
)
}
Now, my problem is -- ALL of the questions still re-render on every keypress...
I thought that perhaps the function I was passing to the Question component was being re-generated so I tried wrapping processValueChange in a useCallback:
const processValueChange = React.useCallback((value, id) => {
setFormData({ ...formData, [id]: value })
}
},[]);
but that didn't help.
My guess is that even though formData (a state object on the FormPage) is not used in the return... its modification is still triggering a full re-render every time.
But I need to store the value of the children so I can do some stuff with those values.
... but if I store the value of the children in the parent state, it re-renders everything and is unacceptbaly slow.
I do not know how to solve this? Help?
How would a functional component store all the values of its children (for validation, layout, etc)... without triggering a re-render on every modification of said data? (I'd only want a re-render if the validation/layout function found something that needed changing)
EDIT:
Minimal sandbox: https://codesandbox.io/s/inspiring-ritchie-b0yki
I have a console.log in the Question component so we can see when they render.

react select is not picking up my selected value using typescript

I am sorry if my question is silly, I am fairly new to Reactjs,
Please do not tell me to useHooks as I can't use it in my current project.
I am working on a form and on it's validations.
my relevant code:
//part of my state
this.state = {
form: {
country: null,
zipCode: "",
},
formErrors: {
country: null,
},
countryList: Array<string>(),
};
}
<Select name="country" defaultValue={countryOptions[1]} options={countryOptions} value={form.country} onChange={(e) => {
this.handleCountryChange({value : e.value, label: e.value })
}}
/>
{this.state.formErrors.country && (
<span className="err">{this.state.formErrors.country}</span>
)}
handleCountryChange(e : countrySelection){
const selectedCountry = e.value;
console.log(selectedCountry);
var formAux = { ...this.state };
formAux.form.country = selectedCountry;
}
but sadly my select keeps looking like this:
Even after I pick a country and I can see it with the console log above. What is missing here in order for the select to pick up the value?
You have to do this something like this
How to set a default value in react-select
Here they did not use defaultValue property
As well as check other properties which they used.
As well as refer this answer of this question too
react select not recognizing default value
You are not setting the state correctly, you can not mutate the state like that. You need to use setState API. See docs: https://reactjs.org/docs/react-component.html#setstate
In your case the event handler should be like this:
handleCountryChange(e : countrySelection){
const selectedCountry = e.value;
// you said this works correctly
console.log(selectedCountry);
this.setState(currentState => ({
// spread the entire state so that other keys in state remain as they are
...currentState,
form: {
// spread state.form so that other keys in state.form remain as they are
...currentState.form,
// only update state.form.country
country: selectedCountry
}
}));
}
Please read the code comments, that should help you understand.

React useState hook - when to use previous state when updating state?

In the code below, the form is not updated correctly unless I pass in previous state to the state setting call ('setForm').
To see it in action, run the code as-is, and the console will print 'true', indicating it worked. That's using 'validate1' function. If you replace that with 'validate2' (which doesn't use previous state), it fails to print 'true'.
It seems like in validate2, the second setForm call overwrites the form.long state of the first setForm call, due to the async nature of these calls. But why does validate1 work? Why does using previous state cause it to work? Any documentation on this behavior would be really helpful.
(I can make validate2 work by having one setForm that sets both fields, but the code below is contrived to show a difference in the two ways setForm can be called, with and without previous state.)
CodeSandbox
const {useState, useEffect} = React;
function LoginForm() {
const [form, setForm] = useState({
username: "",
password: "",
long: null
});
useEffect(() => {
console.log(form.long);
}, [form.long]);
const validate1 = (e) => {
e.preventDefault();
setForm((prevState) => ({
...prevState,
long: form.password.length >=3 ? true : false
}));
setForm((prevState) => ({
...prevState,
username: "*****"
}));
};
const validate2 = (e) => {
e.preventDefault();
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
};
const updateField = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
});
};
return (
<form onSubmit={validate1}>
<label>
Username:
<input value={form.username} name="username" onChange={updateField} />
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);
}
// Render it
ReactDOM.render(
<LoginForm/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
In the React documentation, they mention the following,
If the next state depends on the current state, we recommend using the
updater function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
That's the reason validate1 works, because the second call to setForm depends on the previous setForm call state, and the state will be up to date if the updater function form is used.
Why does validate1 work though? Why does using previous state cause it to work?
setForm({
...form,
long: form.password.length >=3 ? true : false
});
setForm({
...form,
username: "*****"
});
The value of form in the second setForm is still the old value until the next re-render. It doesn't reflect the updated form from the previous setForm.
React setState and useState does not make changes directly to the state object.
setState and useState create queues for React core to update the state object of a React component.
So the process to update React state is asynchronous for performance reasons. That’s why changes don’t feel immediate.
https://linguinecode.com/post/why-react-setstate-usestate-does-not-update-immediately

Resources