Can someone explain me below function line by line
handleChange(e) {
let fields = this.state.fields;
fields[e.target.name] = e.target.value;
this.setState({
fields
});
as my understanding
we are creating a variable fields and storing the present state of fields in the variable because we cant mute the state.
i don't have idea about fields[e.target.name] = e.target.value;
updating the fields
can someone explain me the above function line by line?
fields[e.target.name] this syntax is also used to access the Object Properties.
var obj = {name:'abc', color:'red'}
console.log(obj.name) // this print 'abc'
console.log(obj['name']) // also print 'abc'
with obj['xxx'] syntax you can dynamically read and set properties to the object.
reference https://www.w3schools.com/js/js_objects.asp
So in your problem fields variable take existing fields object from the local state. And in each event it create new property with that event target name and add that event target value as that new property value. if that property name already exist within the fields object then it only modified the value of that property.
So as in your list,
is correct. With let fields = this.state.fields; it get previously stored values into the fields variable. without losing previous data
this is what I explained above
your answer is correct. update the state with old and new values
This code let's you dynamically change the value of a field in your component state by using object notation. object[]
By using fields[event.target.name] you're going to look for a field in your component state that matches the name of the element that's causing the event to occur. Then it looks like you're updating the value for that field with event.target.value
Why this is useful
Let's say you have a component where you want to retrieve multiple inputs from a user. Without object notation, you might end up writing very repetitive code like writing a different event handler for each input in order to determine what field to update in your component state:
BAD:
handleOnChange1 = (event) => (this.setState({userName: event.target.value}))
handleOnChange2 = (event) => (this.setState({lastName: event.target.value}))
However, by naming your elements and coordinating them with a matching field in your component state, you won't have to worry about writing additional event handlers as long as you use object notaton.
GOOD:
class UserForm extends React.Component{
state = {
firstName: "",
lastName: ""
}
handleOnChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render(){
return(
<div>
<input name="firstName" value={this.state.firstName} onChange={this.handleOnChange}/>
<input name="lastName" value={this.state.lastName} onChange={this.handleOnChange}/>
</div>
)
}
}
Related
I have a simple form in a React SPA with typescript.
I'm going to make this form processing simple, without adding component state and so, just to get value from the named input, name = 'gameId'.
There are two ways I've tried in the example below:
If I use event.target, TS says the "children" doesn't exist on type 'EventTarget'
If I use event.currentTarget, TS says the "gameId" doesn't exist on type 'HTMLCollection'.
Is there any way to solve these?
const submitAcceptGameHandler: React.FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();
// 1st
// Property 'children' does not exist on type 'EventTarget'.ts(2339)
const inputData = event.target.children.gameId.value;
// 2nd
// Property 'gameId' does not exist on type 'HTMLCollection'.ts(2339)
const inputData = event.currentTarget.children.gameId.value;
console.log(inputData);
};
<form onSubmit={submitAcceptGameHandler}>
<button type="submit">ACCEPT GAME</button>
<input name="gameId" type="text" placeholder={'Input game id...'}></input>
</form>
The issue with the event.target.children
children is an HTMLCollection, similar to an array. You can get values by its index children[0] or chilren[1]
Either you need to go over all your children or convert it to an array and find the desired component by its name or id
To convert you can just use the spread operator
const children = [...event.target.children]
const gameId = children.find(child => child.name === "gameId")?.value
ANYWAY
I would highly recommend having control over your fields and always having state management for them.
You can even use some libraries to manage your forms.
I prefer using react-hook-form, but you can choose anything you want
First of all, we have a small react based form with three fields (first name, last name, email) followed with a "register" button. Now, the author is using the following code to organise the state.
const [values, setValues] = useState({
firstName: '',
lastName: '',
email: '',
});
And then the author uses the following code to capture typed in values from the field onChange. However, I am not able to fully comprehend what is going on. I would love for someone to help me explain this.
const handleFirstNameInputChange = (event) => {
event.persist();
setValues((values) => ({
...values,
firstName: event.target.value,
}));
};
I am especially perplexed by what's going on with setValues here. For instance, why are we trying to "spread" the values in this scenario? Why is the firstName followed after the spread? Why is the anonymous function body wrapped with parentheses and braces?
Much appreciated.
setValues is the setter from your React state. It sets your email, first name, and password.
It should be more like this.
const handleChange = (event) => {
event.persist();
// the name here would be the key of your state object.
// i.e email, password and firstname
// it should be defined as the name in your input field.
const name = event.target.name;
const value = event.target.value;
setValues((values) => ({
...values,
[name]: value,
}));
};
You could use your fields like
// the name "email" has to be passed through here.
// you could access both the value and the key in your `handleChange` handler
<input type="email" name="email" onChange={handleChange} value={values.email} />
You could check the working example here
React can set state synchronously or asynchronously. This means that your state doesn't necessarily change immediately when you call setValues(). Instead, your state may update a few milliseconds later. It is up to the inner-workings of React to decide when to update your state.
As a result, React allows us to use callbacks when setting the state. The callback will give you access to the previous state which you can then use in your updated state by returning from the callback. With that in mind in your setValues() method:
setValues((values) => ({
...values,
firstName: event.target.value,
}));
Above values is your previous state - so an object. When you spread an object into another object, you're grabbing all the enumerable (own) keys from the values object and adding them to the new object you're spreading into. You can think of this as merging the properties from one object into a new object:
const a = {
aKey: 1,
bKey: 1.5
}
const b = {
...a,
bKey: 2
}
/*
b is interpreted as:
{
akey: 1,
bKey: 1.5,
bKey: 2 <-- Objects can't have duplicate keys, so this one overwrites the above
}
... which then evaluates to:
{
"aKey": 1,
"bKey": 2
}
*/
console.log(b);
Your callback then returns the newly updated object (as this is an arrow function it is an implicit return). Note that the object is wrapped in parenthesis ( ). This is because without the parenthesis the { } for the object literal would be interpreted as a code-block (so the code would be interpreted as a function-body) and not an object literal.
// Arrow function with a body (ie: code-block)
// code-block --- \/
const foo = () => {
};
// Arrow function with implict return
const bar = () => ({ // <--- object
});
A note on event.persist().
React uses SyntheticEvents. A SyntheticEvent is a wrapper to the native event object which gets passed to your event handler. React uses this wrapper for cross-browser compatibility. However, SyntheticEvents are a little different to native events as they're "pooled". Pooling essentially means that the SyntheticEvents are reused, meaning that the same object reference of event can be used for other events throughout your application. Because of this, the object needs to be "nullified" after it's done being used. "nullifying" the object means to make the values of the keys of the object null once the event-handler is finished. So the keys of the event object remain, however, the values of the keys are set to null. This way, when another event triggers, React can grab the SythenticEvent and populate its values. React uses this concept of pooling as creating a new SyntheticEvent instance for each event can be costly, thus, using a reference is more efficient as a new instance doesn't need to be created for each event.
The reason why react nullifies the event is because of performance reasons. When the values are set to null, the old values can be garbage collected which will free-up the application's memory.
So, the properties of event will be nullified once the event-handler has finished executing. As noted, setValues() may be asynchronous, meaning that it may run after your event handler has finished executing and nullifying the event object. As a result, performing event.target.value would result in an error due to the nullification of the event. So, to stop this error from occurring, we can stop the event from being pooled and thus nullified by using event.persist(). Now the event won't be reused and so there is no need for it to be nullified. As a result, another synthetic event will be added to the pool to replace the old one. For your case, persisting the entire event object is a little over-kill since all you want to persist is the event.target.value. This can be done by saving the value of event.target.value in a variable.
const firstName = event.target.value;
When the object is nullified, this won't impact our firstName variable as it is storing a value and not a reference to the value within the object. This way, when you set the state, your callback can close over this variable as use it just fine:
const firstName = event.target.value;
setValues((values) => ({
...values,
firstName
}));
Event pooling isn't something that you'll need to worry about for too much longer as it will no longer be used in React 17 (as it seems that it doesn't improve performance in modern browsers).
i found following example somewhere and I cannot figure out how it is supposed to work, can someone explain it please, I understand the usage of setState and initializing it, but I don't understand the variable prevInputData! what is calling the setInputData and how the previous state is being passed as parameter when setInputData is being called, i'm so confused!
//state stuff
const [inputData, setInputData] = useState({firstName: "", lastName: ""})
//onChange handler
function handleChange(event) {
const {name, value} = event.target
setInputData(prevInputData => { //HOW THIS IS SUPPOSED TO WORK?
return {
...prevInputData,
[name]: value
}
});
}
//form input
<input name="lastName"
value={inputData.lastName}
onChange={handleChange}
/>
This is kind of misleading. It's not actually the state value from the previous render cycle, it's the current state value being passed as an argument to the functional state update callback. By the time the callback is invoked though, during reconciliation, to update state, it is now the state from the last render cycle, and returns the next state value.
Functional Updates
setInputData(prevInputData => { // actually current state when queued
return {
...prevInputData,
[name]: value
}
});
Input data was set at some point right ? Like say at the first run the values are set to be firstname:"jhon" and lastname:"doe" now the next time you run setState it already has reference of the previous value because it was set before.
setInputData(prevInputData => { //Get the previous values which were set
return {
...prevInputData, //uses spread operator to add those values here so that they are not lost
[name]: value //setting new values.
}
});
In other words if you dont use prevInputData which is just a variable name it can be anything, your previous changes will be overridden. Also by run i mean the current state it holds, not like you could come back after a refresh and it'll be there. its like updating for input boxes, with each input box you leave, the state is updated and retains the previous input box values you added, if you dont do ...prevData then the previous values you entered will be lost.
I'm trying to update the state of the id value in shoeList. Currently I have a textfield that allows for entering of a new ID and I want the state to update when the OK button is clicked.
Here is some of the relevant code:
state = {
editingToggle: false,
shoeList : [
{name: 'bob', id: '123213-0', shoeSize: 'L'}
],
}
<TextInput className='text-area-header' id="textM" width="m" type="text" placeholder={this.state.shoeList[0].id} />
<Button className="ok-button" variant="tertiary" size ='xs' type="button" handleClick={this.saveHeader}>OK</Button>
saveHeader(e) {
this.setState(state=> ({
shoeList[0].name:
}))
alert('Header changed to ' + this.state.shoeList[0].id);
e.preventDefault();
}
I'm not sure what to put in this.setState as I haven't found anything on how to update nested values through a google search. Also whenever I put a value attribute to the TextInput tags it doesn't allow me to edit in the textinput on the webpage anymore. Any help would be great
Consider this example:
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.setState({ shoeList: this.state.shoeList })
}
setState() checks if the values have changed, and if not, it does not update the component. Changing a nested value does not change the reference to the array, which means that simply calling setState() is not enough.
There are two ways around this. The first is to use forceUpdate():
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.forceUpdate()
}
This forces the component to re-render, even though the state didn't change.
The second way is to actually change the shoeList array by creating a new one:
saveHeader() {
let newShoeList = this.state.shoeList.slice()
newShoeList[0].id = 'newid'
this.setState({ shoeList: newShoeList })
}
Using .slice() on an array creates a new array that is completely identical. But because it is a new array, React will notice that the state changed and re-render the component.
I am trying to submit a form that takes form field inputs into a React hook state object. However, the payload object is accessed by other components in the application that expect the payload object to be a certain format.
const [formInput, setFormInput] = useState();
const onChange = event => {
event.target.name = event.target.value;
}
return (
<form onSubmit="props.onSubmit">
<label>First Name</label>
<input value="formInput.first-name" name="first-name" onChange={onChange}></input>
<input value="formInput.person-dept" name="person-dept" onChange={onChange}></input>
<button type="submit">Add a cadet</button>
</form>
)
So, the formInput object has two properties. But they need to be nested, like this:
//acceptable payload:
{cadet: [
first-name: '',
dept: ''
]
}
I have tried calling a function for wrapping them using a new state object, but it gives me an undefined error for the schema property:
const schema = () => {
cadet: [
first-name: '',
dept: ''
]
}
const [formattedInput, setFormattedInput] = useState(schema);
const updateInput = () => {
setFormattedInput(
cadet: {
{first-name: {formInput.first-name} || null},
{dept: {formInput.person-dept} || null}
}
)
}
updateInput();
api.post('~/person/cadet', updateInput);
In the above example, the properties from schema are undefined, cadet and first-name.
Also, in order to set the setFormattedInput object before calling the API I need to instantiate the function that has it, but because of React rules, calling updateInput(); runs when the component is rendered and is undefined (sort of like needing a componentDidUpdate() for a functional component).
This should be very common- we all need to reformat our form state objects before they reach the API unless you are building an application from scratch. Does anybody know how?
To give some context, the NPM package mapper does what is needed, but it simply doesn't work (https://www.npmjs.com/package/mapper).
I ended up using a custom wrapper and fixing minor syntax issues along the way. In the payload,
api.post('~/person/cadet', formInput);
I added
const formattedObj = formatted(formInput);
api.post('~/person/cadet', formattedObj);
console.log(formattedObj);
Then in the same file I defined formatted like this:
const formatted = (formInput) => {
const payload = {cadet: [
first-name: formInput.first-name,
dept: formInput.person-dept
]}
return payload;
}
Therefore, formatted() gets passed in the state object and payload is behaving sort of like a block variable and doesn't need to be called for it to be the data being returned by formatted().
Just a minor step of wrapping the object passed to the URL path and some syntax in formatted() and the basic workflow was correct.
I wanted to share because it took several powerful coworker minds to make this work and it should be taught with React hooks everywhere to lower the barriers to entry.
Thanks for reading and I hope this works for you, too!
P.S.- If the object isn't being saved, it's due to other syntax issues in what you are passing into the object from the form fields. Also, minor syntax tweaks will get this solution to work if something is a tiny bit off.