react checkbox: event.preventDefault() breaks onChange function - why? - javascript

I have just found a stray event.preventDefault() that was breaking my checkboxes' onChange handler:
import { Component } from 'react';
class App extends Component {
constructor(props) {
super(props)
this.state = {
accepted: false
}
}
changeChecked = (event) => {
this.setState((state) => ({
accepted : !state.accepted
}));
event.preventDefault(); // <- this very bad
}
render() {
return (
<input
type="checkbox"
onChange={this.changeChecked}
checked={this.state.accepted}
/>
);
}
}
export default App;
The resulting behaviour is a correctly updated state on first click, but the checkbox only changes to its 'checked' appearance on the next rerender, eg. a second click.
Why is that? Isn't it the point of controlled components to work independently from browser events?
Someone explaining this to me would definitely ease the pain of hours spent boiling down my complex use case. Thank you!
Update: Here's a quick Codepen example demonstrating the odd behaviour.
I included an 'unprevented checkbox' and one with a prevented onClick event as comparison.
Notice how the one with prevented onChange switches its appearance to its actual state as soon as I click a different checkbox.

Checkboxes behave a little differently. As you likely know, the typical use case for preventDefault() is the onSubmit() function of a form where you will do your own AJAX call, and thus want to prevent the default form submission. But with checkboxe (and most inputs) there's a little more involved.
Checked attribute
Per MDN, the checked attribute is "A Boolean attribute indicating whether or not this checkbox is checked by default (when the page loads). It does not indicate whether this checkbox is currently checked: if the checkbox’s state is changed, this content attribute does not reflect the change." In a funny way then, there's a disconnect between the checked attribute, and whether or not the state of the input is checked or not. When it comes to React, on each rerender, the checked attribute will reflect the current state, but it's still only a default value in the sense that the input has been newly rendered, and not manipulated since the state last changed.
Native eventListener for <input type="checkbox" />
Also, without getting too off track, the event that natively changes the state on a checkbox input is actually the click event, not the change event. If you were to mess with the listeners and the values of a checkbox input in your browser's js console, you'd see you could manipulate it in a way that the checkbox isn't checked, but the values of the node say otherwise.
Possible solution
Based on the above, in this case you don't want to prevent the default behavior, because the default behavior on the change event doesn't conflict with what you want to do (and for some reason preventing it causes problems). In fact, in the React docs examples, you'll notice they don't use preventDefault() when updating state on controlled components. I wish I had a better understanding of exactly why adding preventDefault() to change handlers for inputs causes problems like this, but hopefully these few little tidbits give some more clarity.

Try moving event.preventDefault() above this.setState line.

Related

Is this a controlled or uncontrolled React component?

I read the answers on this question but none is similar to my set up:
What are controlled components and uncontrolled components?
I have a parent component and many children as inputs. Each input has a defaultValue that is set using a prop received from the parent and the validation of the input is performed on onBlur. This is because the validation is asynchronous, so onChange would slow down the app like crazy.
How is this orchestration called? The traditional definition for "controlled" seems to be an input which updates on every onChange, while an "uncontrolled" input is one which manages its own internal state, which is later accessed via a ref. My set up seems to fall short of both, sitting somewhere in the middle - or not?
Update: Here's a CodeSandbox.
If I understand your setup correctly, your inputs are uncontrolled, because their state is held in the DOM rather than in React state. Validation is a separate concern, which can happen synchronously or asynchronously. You can let React state hold the values whatever you do for validation. Note that most of the time you don't want to prevent the input from even having an invalid value - you just want to ensure that the user can't submit the form while the values are invalid. Thus you can have an onChange handler to set some value on state as in the following:
<input type="text"
value={this.state.myValue}
onChange={val => this.setState({myValue: val},
()=> this validateMyValue(this.state.myValue))}} />
this.validateMyValue could be asynchronous, and do whatever is required if validation fails. That would be a controlled component doing what you want.

How to stop reactjs component rerender on state change

I have a form with some input fields (some of them hidden until a checkbox is checked. When I check/uncheck the checkbox a specific state is set (this.state.isChecked = true/false).
On render method, i have a div (containing some input fields) with some classes and this condition for "show" class: <div className={'extra-fields ' + (this.state.isChecked? ' show' : '')}>...</div>
Expected behavior is that on state change, only the "show" class is putted or deleted from div
Current behavior: the entire form is rendered again and all data written in the input fields are lost.
How it should be done this?
If the value of the input fields is important (which they apparently are), and if they can change (which the obviously can), then React should be aware of them, typically in state.
The 'standard' (on only) react way to maintain the contents of the input fields is:
put the content of the input fields in state as well,
include something like value={this.state.foo} and onChange={this._onChange()} to the render of each input field
include the _onChange() function to the form to handle input changes
That way, whenever the form is re-rendered (after each setState()), the input values are also preserved.
PS: The question title "stop reactjs component from rerender on state change" does not really cover the question from text: you are asking for a partial re-render: do re-render the show/hide extra fields based on checkbox, but do not re-render input fields.
The short answer is that you can prevent a component from rendering/updating with the shouldUpdateComponent life-cycle event: https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
However, I agree with the previous answer, that for the sake of doing what sounds like a partial re-render, and to follow best practices in general, you should be storing the state of your input fields somewhere in a component so that they persist beyond a render call.

Why does React pre-populate input tags with the last keyboard input?

I have created a list of input tags wrapped in a div to produce an editable text list.
However, when I add a new element to this list by hitting Enter, the new empty element is automatically populated with the last keyboard input, instead of being blank.
Here is a link the JsBin describing the issue:
https://jsbin.com/kovipurace/edit?html,output
I am expecting an empty input instead.
Is there a way to implement the expected behaviour correctly?
I initially based my code off of the CommentForm in the react tutorial, but I have deliberately not used a form tag to give cleaner use of the hitting the Enter key (don't want to mess around with e.preventDefault() etc.) I have also put in additional functionality in the actual version to allow updating existing values using an onBlur event.
What I have tried:
I have (correctly, hopefully!) implemented controlled components as stated in the docs.
I have experimented with using defaultValue and instead of value but the effect is the same. In both cases I am using {this.props.value}
You create new textentries with textEntry text.
keyHasBeenPressed : function(e) {
if (e.keyCode == 13) {
var textEntry = this.refs.textEntry.value;
this.props.createEntry(textEntry);
}
},
All you need is to make the args of createEntry blank.
this.props.createEntry();
https://jsbin.com/daqura/1/edit?html,output
I found help on #reactjs IRC channel and commenters here.
In the keyHasBeenPressed function, the state needs to be set explicitly again, as it is in handleChange.
getInitialState is just used to intialize state once in a component's life cycle.

Aurelia one-way binding to a checkbox

I'm looking for a way to one-way bind a checkbox in aurelia, whilst still allowing the checkbox to accept a click from the user.
Assume a view similar to the following that displays one of a list of selectable items:
<template>
<div click.trigger="selected()">
<label......>${vm.code}</label>
<label....>${vm.description}</label>
<img...../>
<input type="checkbox" checked.one-way="vm.selected"></input>
</div>
</template>
The user should be able to click anywhere in the view to select the item, thus the click.trigger="selected()" is attached to the container. Within selected(), the vm.selected property that the checkbox is bound to is updated.
The checkbox should also be clickable as well, but should allow selection to be controlled by the selected() method.
readonly can not be used on the input control for the checkbox as that is used to control input.value and in this case it is the checked property that is of interest.
Calling preventDefault on the event args disables default checkbox checked behavior, which can be achieved in Aurelia by returning false from the click delegate. That would require attaching another handler to the input control, and has the problem that the click gets handled by the input control and doesn't bubble up to the delegate attached to the container (selected()) to actually control selection.
Maybe there is another approach to this that I am missing, one that I was considering was to use two font-awesome icons that look like checked and unchecked checkboxes and switch between the two based on the value of vm.selected.
Update
On the delegation of events this answer does solve the issue if you are not using a checkbox but the browser prevents the state from toggling on the checkbox so as we discussed the best approach is probably to use an icon or sprite to show the 'state' of the checked piece since the control should be one-way only, which can be accomplished like this -
<i class="fa fa-${this.selected ? 'check-square-o' : 'square-o'}"></i>
Original answer on delegation
If we use delegate instead of trigger then our event bubbles and we can handle it a bit more gracefully and prevent the state from changing -
<div click.delegate="clicked()">
<input type="checkbox" click.delegate="clicked(false)" checked.one-way="vm.selected" />
</div>
And then in our view model we can handle the click event that bubbles -
clicked(event){
if (event !== false) {
// do something
} else {
return false;
}
}
This isn't a perfect solution but it should prevent the event from occurring which changes the state of the control but still allow the event to take place at the div level.

After execution of onclick-callback the 'checked' state is reset

This drives me mad. I just can't understand it.
I wrote a filter-function based on checkboxes and clicking on their labels. I'm checking the 'checked' state of checkboxes and show mathed elements of the list (the rest elements are hidden). I use 3rdparty plugin that stylizes checkboxes (cut from example) and makes checkboxes checked while other onClick event does the filtering.
The problem is that after 'checked' state is successfully set inside a callback-function it "suddenly" becomes reset! I can't understand why that happens.
I implemented the base logic (without stylizing) here: http://jsfiddle.net/3Xtuh/13/
and ask all to help me solve this, please.
The problem is that you are invoking the click event manually, and then when your function is done running, the default click event is invoked.
By passing the event variable to your click handler and calling event.preventDefault(); fixes this behavior.
See example here: http://jsfiddle.net/3Xtuh/14/
The HTML label will check the associated checkbox even if it´s hidden (using CSS) so there´s no need to reinvent the wheel.
You should use the change() event. Try this demo and view your console.
It's default browser behavior that's messing your js script. By default, clicking on a label that is either wrapped around checkbox or have valid for attribute set, is toggling checked state of that checkbox.
You've attached custom onclick handler on labels.
So what's going on is that when clicking on a label? Your click handler gets fired (in in you alter state of target checkbox), and then

Categories

Resources