Why does the newer 'setState' method not recognize my event.target? - reactjs

Is the newer setState syntax not recommended for use with events?
While debugging, I saw that the first letter I typed came through with
a proper e.target.value.
Immediately afterwards though, I got the TypeError you see below.
// onChange(e) {
// this.setState(prevState => {
// return { username: e.target.value} <--- TypeError: Cannot read property 'value' of null
// })
// }
// obviously the setState below works fine
onChange(e) {
this.setState({ username: e.target.value});
}
<input type="text"
placeholder="What is your username?"
onChange={this.onChange}
// onChange={e => this.onChange(e)} - also tried this
value={this.state.username}/>

You are using the event in an asynchronous way by accessing it in the callback function you passed to setState(). By the time react calls your updater function all properties on the event have already been cleared.
From the react docs:
The SyntheticEvent is pooled. This means that the SyntheticEvent
object will be reused and all properties will be nullified after the
event callback has been invoked. This is for performance reasons. As
such, you cannot access the event in an asynchronous way.
To avoid that you either need to persist the event by calling event.perist(). This will enable you to use it later.
Or you assign the value you are interested in to a local variable that can be used asynchronously:
onChange(e) {
const username = e.target.value;
this.setState(prevState => ({username}));
}
Actually in your specific example you do not need the callback based version of setState() at all as you aren't updating the state based on previous state or props.

Update
I missed the error in the first place which is e.target is being null here. So, the real problem is not extracting the value from the event or using event.persist() there as #trixn explained in his/her answer. But, my answer includes this step though. So, it worked :)
Neither of them works if you don't bind your onChange function for this. Here is the working version of your code that you say does not work:
class App extends React.Component {
state = { username: "" };
onChange = (e) => {
const { value } = e.target;
this.setState(prevState => {
return { username: value}
})
}
render() {
return (
<div>
<input type="text"
placeholder="What is your username?"
onChange={this.onChange}
value={this.state.username} />
<p>Username: {this.state.username}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I just used an arrow function instead of implicitly binding it. So it is bond automatically. Also, I extracted the value from e.target for React's synthetic events handling. We can't use events properly if we use them in a callback function like in setState's here. This is why I extracted it then use as a separate variable.

Related

What's the difference between using a function to return an object in setState and using an object directly?

In the following, the handleChange method throws an error when, inside setState, I use a function to return an object, but works fine when I update inputvalue directly as an object
class App extends React.Component {
constructor (props) {
super(props);
this.state = {
inputvalue: ''
}
this.handleChange = this.handleChange.bind(this);
}
handleChange (event) {
event.preventDefault();
this.setState(() => {
return {inputvalue: event.target.elements.name.value}//throws an error but works fine if I use just use object with out using a functon to return
}
);
}
render() {
return (
<div>
<form onSubmit={this.handleChange}>
<label>Name </label>
<input type="text" name="option" />
<button>submit</button>
</form>
<p> {this.state.inputvalue}</p>
</div>
);
}
}
render(<App />, document.getElementById('app'));
Warning that happens:
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the method stopPropagation on a released/nullified synthetic event. This is a no-op function. If you must keep the original synthetic event around, use event.persist(). See https://reactjs.org/docs/events.html for more information.
Based on this:
the SyntheticEvent is pooled. This means that the SyntheticEvent
object will be reused and all properties will be nullified after the
event callback has been invoked. This is for performance reasons. As
such, you cannot access the event in an asynchronous way.
If you want to access the event properties in an asynchronous way, you
should call event.persist() on the event, which will remove the
synthetic event from the pool and allow references to the event to be
retained by user code.
You should call persist method when using a function as updater.
handleChange (event) {
event.persist();
this.setState(() => {
...
}
}
Take a look at this answer:
TypeError: evt.target is null in functional setState
i think u are accessing here wrong:
event.target.elements.name.value
should be
event.target.elements.option.value // because u set the name attribute to option (name="option") on the input element
and if i don't mistake the error is that u trying to access null reference right?

How do I update React context from within an onChange handler function

There are plenty of examples of changing context directly - such as this:
export class UserProvider extends React.Component {
updateUsername = newUsername => {
this.setState({ username: newUsername });
};
state = {
username: "user",
updateUsername: this.updateUsername
};
}
followed by this in the Consumer:
<input
id="username"
type="text"
onChange={event => {
updateUsername(event.target.value);
}}
/>
but my onChange handler is a function and within that OnChange handler, I want to update a context variable.
Using this example, how would I call updateUsername from within the OnChange Handler or indeed from any function such as componentDidMoount, etc
I have searched but so far not been able to find anything that might give an indication as to how to accomplish this.
Using Context in React you'd usually want to have reducers when updating state, reducers allow you to dispatch actions that mutate state repetitively. Don't update state directly it's regarded as anti-pattern.
I wanted to add this a comment however my reputation doesn't allow me to do so yet, excuse me if this doesn't solve your problem.

Controlled input changing on setState()

I have this constructor in React component:
constructor() {
super();
this.state = {
info: {
title: '',
description: '',
height: ''
}
}
...
And a form with inputs controlled by the state:
<form onSubmit={this.handleFormSubmit}>
<label>Title:</label>
<input type="text" name="title" value={this.state.info.title} onChange={(e) => this.handleChange(e)} />
<label>Description:</label>
<input type="text" name="description" value={this.state.info.description} onChange={(e) => this.handleChange(e)} />
...
When I type anything on the form, I guess there's something wrong with my handler, as I get the warning "Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component."
Checking console, it seems state is updating each property value that is being typed, and removing other properties, while it should remain all of them and only update the changed ones.
Here's my handler:
handleChange(event) {
let { name, value } = event.target;
this.setState({
info: {
[name]: value
}
});
}
the one you are using is updating only one props and the others being stripped that causes React shows warning. you can use spreading to keep the others
handleChange(event) {
let { name, value } = event.target;
this.setState({
info: {
...this.state.info,
[name]: value
}
});
}
There's usually 2 cases which cause such warnings to manifest in my experience.
The initial value of the property in state is undefined and not being changed when updated
The initial value is '' and being changed to null
Try this:
handleChange(event) {
let { name, value } = event.target;
this.setState(({info}) =>({
info: {
[name]: value
}
}));
}
Breaking down the function call:
First you have this.setState(updaterFunction). updaterFunction gets called by setState with the previous state as the argument, and that function is expected to return an object with the keys of the state to update (it gets merged shallowly with the previous state).
Because setState is only a shallow merge (no matter if you pass it an object or a function), if you have an object at like this.state.foo.bar and you update the state with an object like {foo: {bar: 'qux'} }, the old foo will not get merged with the new foo, it will instead get replaced. So your updater function needs to do the deeper merging manually.
The updaterFunction looks like this ({info})=>({…}). We pull info out of the previous state, and we return an object, using info to manually do the deeper merging.
The benefit of passing a function to setState (instead of just an object) is that if you use the state passed to the function instead of this.state, you will avoid some potential bugs when multiple setState calls get batched together....

What is the correct way to wire up an input field in Redux

I have the following component in a Redux app for recipes, which currently only has a name right now, for simplicity sake.
class RecipeEditor extends Component {
onSubmit = (e) => {
e.preventDefault()
this.props.updateRecipe(this.props.recipe, { name: this.refs._name.value })
}
render = () => {
if (!this.props.recipe) {
return <div />
}
return (
<div>
<form onSubmit={this.onSubmit}>
<label>Name: </label>
<input type="text" ref="_name" value={this.props.recipe.name} />
<input type="submit" value="save" />
</form>
</div>)
}
static propTypes = {
recipe: React.PropTypes.shape({
name: React.PropTypes.string.isRequired
})
}
}
This gives me an editor with a textbox that can't be edited. There's a warning in the console as well:
Warning: Failed form propType: You provided a value prop to a form
field without an onChange handler. This will render a read-only
field. If the field should be mutable use defaultValue. Otherwise,
set either onChange or readOnly. Check the render method of
RecipeEditor.
That makes sense, but I don't want an onChange event, I'll use ref to get the values on submit. It's not a readonly field obviously, so I try changing it to have a defaultValue.
<input type="text" ref="_name" defaultValue={this.props.recipe.name} />
This gets closer to the behavior I'm looking for, but now this only sets the recipe when the control is mounted and it no longer updates when a new recipe is chosen.
Is the solution having a handler on every input field that sets state, and then in submit, take all the state and update the recipe?
When you use an input element with the valueattribute set, it becomes a "controlled" component. See this page for a more detailed explanation:
https://facebook.github.io/react/docs/forms.html#controlled-components)
Long story short, that means that on every render you are setting the value attribute to the value from the props, which will stay the same unless you also update the value in your redux store).
When the input is "uncontrolled" instead (value attribute not explicitly set), its internal state (the value string) is handled implicitly by the browser.
If for some reason you prefer to keep the state locally and you don't want to dispatch a redux action every time the value changes with onChange, you can still manage the state yourself using React component state and dispatch the action on submit:
class RecipeEditor extends Component {
state = {
recipeName: ''
}
onSubmit = (e) => {
e.preventDefault()
this.props.updateRecipe(this.props.recipe, { name: this.state.recipeName })
}
handleNameChange = (e) => {
this.setState({ recipeName: e.target.value })
}
render = () => {
if (!this.props.recipe) {
return <div />
}
return (
<div>
<form onSubmit={this.onSubmit}>
<label>Name: </label>
<input type="text" ref="_name" value={this.state.recipeName} onChange={this.handleNameChange} />
<input type="submit" value="save" />
</form>
</div>)
}
static propTypes = {
recipe: React.PropTypes.shape({
name: React.PropTypes.string.isRequired
})
}
}
In this example, whenever the input value changes you store the current value in the state. Calling setState triggers a new render, and in this case it will set the value to the updated one.
Finally, note you don't have to use onChange if you never need to set the input value to something specific. In this case, you can remove the value attribute and just use refs. That means though that if somewhere else in your code you change the value stored in Redux state, you won't see the change reflected in your input. As you've seen, you still can set the initial value to something specific using intitalValue, it just won't be in sync with your redux store.
However this is not going to work well if for example you want to reuse the same form to edit an existing recipe you have in your store.
As React already mentioned in the console, you have to provide an "onChange" listener to make that field editable.
The reason behind that is the value property of the input field which you provided
{this.props.recipe.name}
This value doesn't changes when you type in the input field.
And since this doesn't changes, you don't see the new value in your input field. (Which makes you feel that it is non editable.)
Another thing to note here is that the "recipe.name" should be transferred to the state of your component so that you can set a new state inside "onChange" listener.
see the usage of "setState" function.

React state update step behind

This color picker works but one step behind. I've been using React 15.4.2.
Is it an issue to be fixed in later versions?
In case it's my fault, please, how to master "pending state condition"?
Pen http://codepen.io/462960/pen/qrBLje
Code:
let Input = React.createClass({
getInitialState(){
return {
today_color: "#E5D380"
};
},
colorChange(e){
this.setState({
today_color: e.target.value
})
document.querySelector('html').style.setProperty('--base', this.state.today_color);
},
render(){
return (<div>
<input className="today_color" onChange={this.colorChange} type="color" defaultValue={this.state.today_color}/>
</div>)
}
})
The issue you are having is that once you call setState the component rerenders and this code isn't called again:
document.querySelector('html').style.setProperty('--base', this.state.today_color);
And the first time it is called, this.state.today_color is still the previous value.
What you should do is the following:
this.setState({
today_color: e.target.value
}, () => document.querySelector('html').style.setProperty('--base', this.state.today_color));
This makes sure the setProperty gets called after setState is done and after you have the correct value in your state.
Edit: here's a working example.

Resources