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.
Related
What is value={} for in React inputs?
I currently have a state of const [username, setUsername] = useState("");
and an input element in my form that updates whatever the user is typing in the input to the state.
<input type="text" value={username} name="Username" onChange={({target}) => setPassword(target.value)}/>
Why do i need to specify the value in the input? What is the purpose of having the value be the new state variable? It seems to work fine without the value={} at all if I remove it
Doing it with a value prop is called a "controlled component", and otherwise it's an "uncontrolled component". Both of these approaches are supported in react, but the controlled component gives you more... well... control. If you ever need to do something deviates from the built in behavior of the browser, then you must use a controlled component.
So for example, if you want to take what the user typed and make it upper case, you need a controlled component. If you want to have a button which clears the text of your inputs, you need a controlled component. The only case that works with uncontrolled components is if you want literally what the user types.
https://reactjs.org/docs/uncontrolled-components.html
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.
I have a simple hidden input field in my React Redux-Form and the value of the field is set by some Google Tag Manager code that basically does document.getElementById('gtm_field').value = 'some value'
The onchange event never gets fired and therefore the value of the form field never gets set in my Redux store. What is the recommended way to approach this problem? Note that I can modify the GTM code that is added to the page do trigger HTML element events, but it has to be added 'outside' the context of my React app.
Is there a simple way to set the value of a redux-form input field from 'external' JS?
You should use the function change() in redux-form props, it could change the value of a field in the Redux store. The usage like this:
handleChange() {
this.props.change(field:String, value:any)
}
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.
I'm using dust.js in order to render Javascript templates to HTML. Using dust.js, I've basically broken my page into template components, that are rerendered in response to certain events. One such component/template conceptually looks as follow:
[Label] [Input1] [Input2] [Input3]
All 3 inputs in this component have an onBlur event which is used to recalculate the value of the other inputs and which in turn causes the template to rerender. So for instance, if the user modified the value of Input1, both Input2 and Input3's values will be adjusted and rerendered.
The reason I rerender the template instead of just setting the values is that the template contains additional logic and elements that are rendered depending on the values (so for instance, certain values would result in an error message being rendered or an additional field being displayed).
So far so good, but this causes a focus problem. Take for example if the user modifies Input1 and then clicks on Input2. The onBlur will trigger, I'll update the values of Input2 and Input3 and then rerender the template. This in turn will cause the user's focus to be discarded, and they'll have to click on Input2 again.
I need a way to know that the user clicked on Input2 so that I can set the focus to it after the rerendering has been completed. As the onBlur event triggers before onFocus, I can't think of any way to do this.
Is there any solution other than bypassing the template rendering?
I had a similar problem. I solved it by delaying the "blur" event listener function with about 100ms, to allow the focus event on another input to fire before the blur handler starts rendering.