How is React handling renders for this uncontrolled input element? - reactjs

I'm going through React's beta docs and would like to better understand how react handles user's interactions with the UI. Specifically with this non-controlled input field.
Note:
the input field is not disabled
new input values should not trigger a render, because it's not changing any state (local variables are ignored)
So, then why is the change not reflected in the UI???
The code is bellow, and an interactive "sandbox" can be found here: https://beta.reactjs.org/learn/state-a-components-memory under challenge #2 at the bottom of the page.
export default function Form() {
let firstName = '';
let lastName = '';
function handleFirstNameChange(e) {
firstName = e.target.value;
}
function handleLastNameChange(e) {
lastName = e.target.value;
}
function handleReset() {
firstName = '';
lastName = '';
}
return (
<form onSubmit={e => e.preventDefault()}>
<input
placeholder="First name"
value={firstName}
onChange={handleFirstNameChange}
/>
<input
placeholder="Last name"
value={lastName}
onChange={handleLastNameChange}
/>
<h1>Hi, {firstName} {lastName}</h1>
<button onClick={handleReset}>Reset</button>
</form>
);
}
To be clear, I'm not asking how to solve this problem; I know how to hook it up and fix it with state. I am interested in what's preventing the input field from updating with a new value.
On initial render it receives the empty string as its value. But then why doesn't it change when a user types?
Here's two non-react input fields, one with a value attribute, and one where the value is set by JS. Both can be modified:
<input type="text" value="editable">
<input type="text" id="input2">
<script>
document.getElementById('input2').value = "also editable"
</script>
So, why isn't the input editable in react?

There are two reasons for a component to render:
It’s the component’s initial render.
The component’s (or one of its ancestors’) state has been updated.
In your example, the second condition is not met and react has no way of knowing your input values changed, it does not re render, and so does not commit changes to the DOM.
Note that react is wrapping event handlers, it does not work exactly like vanilla JavaScript

Related

Not getting any console.log data from first name field box

import React, { useState } from "react";
import "../styles.css";
const FormBox = () => {
const [value, setValue] = useState({
fName: "",
lName: "",
});
const [textArea, setTextArea] = useState("");
const handleSumbit = (e) => {
e.preventDefault();
console.log(value);
console.log(textArea);
};
return (
<div className="center">
<form onSubmit={handleSumbit}>
<div>
{/*This is how to set a control form box */}
<input
placeholder="First Name"
type="text"
value={value.fName}
onChange={(e) => setValue(e.target.value)}
/>
</div>
<div>
{/*This is how to set a control form box */}
<input
placeholder="Last Name"
type="text"
value={value.lName}
onChange={(e) => setValue(e.target.value)}
/>
</div>
<div>
<textarea
value={textArea}
onChange={(e) => setTextArea(e.target.value)}
></textarea>
<div>
<input type="submit" />
</div>
</div>
</form>
</div>
);
};
export default FormBox;
I'm working on a basic React project and I'm currently getting the message "Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
input"
Another problem is that I'm not getting any info when typing in first name field box. I do get back last name and textarea.
Thank you so much and happy holiday
You are having two issues:
Not getting info from the first name field
The uncontrolled vs controlled warning
Issue 1.
This actually happens because on both the first name and last name inputs you are doing setValue(e.target.value). setValue() sets the object that is originally initialized as { fName: '', lName: '' }. This means that even though the value is an object at first, when writing on any of the fields, it will become a simple string, either the first or last name.
This problem does not happen with the text area, since you keep track of its value on a separate state, textArea. That's one way to fix your issue, hold both fName and lName on separate useState calls. Another way is to replace the old value object with a new one that works as expected, for example:
<input
placeholder="First Name"
type="text"
value={value.fName}
onChange={(e) => setValue({ ...value, fName: e.target.value })}
/>
I would also suggest avoiding abbreviations whenever possible. You could've used firstName and lastName instead as variable names. It really goes a long way improving readability.
Issue 2.
This warning is a side effect of the first issue. It appears because you are using controlled inputs but when writing on one of them the object is turned into a string and both value.fName and value.lName are undefined. Controlled inputs are not supposed to have undefined as a value, since that's usually an indicator that you want to use uncontrolled inputs. The empty value should be an empty string, which was intended and will be fixed if you fix the first issue.
There's another stack overflow sharing the differences between controlled and uncontrolled components: What are React controlled components and uncontrolled components?
A Controlled Component is one that takes its current value through props and notifies changes through callbacks like onChange. A parent component "controls" it by handling the callback and managing its own state and passing the new values as props to the controlled component. You could also call this a "dumb component".
A Uncontrolled Component is one that stores its own state internally, and you query the DOM using a ref to find its current value when you need it. This is a bit more like traditional HTML.
The important bit to note here is that an uncontrolled component doesn't receive a value prop and hence its value is undefined.

A single onChange listener on a <form> tag

I was just playing a bit with a more generic way of dealing with form data, and figured that setting a single onChange listener on the wrapping form tag should work for all changes to input fields.
And indeed, it does - no surprise there. Events get called, I know what changed by inspecting event.target.name and can update state accordingly. Fully as expected.
However, React doesn't seem to like it and spams the well known "You provided a value prop to a form field without an onChange handler" console warning.
Would there be any other reason for not doing this, apart from the console warning? It seems to eliminate a lot of duplication React otherwise gets criticised about.
class App extends Component {
state = {
name: 'Default Name',
number: 12,
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
const { crashLogo } = this.props;
return (
<div className="App">
<form onChange={this.handleChange}>
<input type="text" name="name" value={this.state.name} />
<input type="number" name="number" value={this.state.number} />
</form>
</div>
);
}
}
Just for clarity: I'm not asking for alternative solutions, I know I can set the same change listener directly on every input, use redux-form, own wrapper components with context magic and so on...

questions about react controlled text field?

You can see below sample code, it's a typical controlled field.
export default class NameForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<form>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
I can understand that if I type any text in the field, the value will be reflected into the text field due to the setState function and the value props of the input.
But if I changed the input to below. I removed the value props.
<input type="text" onChange={this.handleChange} />
Currently, If I typed any value in the field, the value will be still there, but as per my knowledge of React, the input will be rendered after the setState, but the props value is not set, the input will be cleared in my opinion, Could you please explain it in detail for me?
I think you miss the important point here. After setState is called the component render will be called - but this will not recreate the input component from scratch. Internally react will check and apply only changes that has happened to the actual DOM.
In your case no differences to the input were made - therefore it will stay as it is.
This process is called reconciliation and you can read more about it here.
Setting value on an input element makes it a controlled input. Controlled inputs always show the value provided in their prop. For example if you have this input:
<input value='constant' onChange={({target: {value}}) => this.setState({value})}/>
User can't change its value. but if you don't provide value prop for your input it's not a controlled value and it shows the value user enters in. This same thing is true for checkboxes, radio buttons, etc.
So in your case it updates based on user input because it's not a controlled component (because you didn't provide value for it.)
If you want to set initial value for a component but keep it uncontrolled (user can change the value of component) you can set the initial value with defaultValue prop.
You can read more here:
https://facebook.github.io/react/docs/forms.html
You shouldn't be setting the value, just the defaultValue, like this
<input type="text" defaultValue={this.state.value}
The handleChange will then make sure the state is updated

Having issue rendering components with radio buttons multiple times

A bit of context first, I'm working a questionnaire and decided to start learning React.js. Each step of the questionnaire includes a question and a yes/no radio button group, and also navigation buttons. Here's the jsfiddle. The question component is pretty straight forward, I'm passing the formData and save data function to it in the parent component.
And code for the question component
var Question = React.createClass({
getInitialState: function() {
return {
}
},
createMarkup: function(html) {
return {
__html: html
};
},
render: function() {
var subtitle = this.props.subtitle || '';
var name = this.props.name;
if (this.props.formData[name] === undefined) {
var answer_yes = null;
var answer_no = null;
} else {
answer_yes = this.props.formData[name] == "yes" ? true : null;
answer_no = this.props.formData[name] == "no" ? true : null;
}
console.log(answer_yes);
console.log(answer_no);
return (
<div className="container-question">
<label className="default" dangerouslySetInnerHTML={this.createMarkup(this.props.question)}></label>
<span className="alert subtitle" dangerouslySetInnerHTML={this.createMarkup(subtitle)}></span>
<div className="form-group form-group-radio">
<div className="row">
<div className="col-xs-4">
<input type="radio" id={name + "_yes"} name={name} value="yes" defaultChecked={answer_yes} onChange={this.props.saveData}/><label htmlFor="yes">Yes</label><br></br>
<input type="radio" id={name + "_no"} name={name} value="no" defaultChecked={answer_no} onChange={this.props.saveData}/><label htmlFor="no">No</label>
</div>
</div>
</div>
</div>
);
}
});
The problem is that when you select your answer on the first question and click on next, the radio control on the second question change with it, and same thing happens when you answer second question first. I'm storing the boolean variable for defaultChecked as local variables in the render function and logging them in the console.
Is there any obvious mistake i'm making? any help is appreciated.
Here is an updated jsfiddle that works: https://jsfiddle.net/69z2wepo/8365/
The issue is that you're using what is a called an 'uncontrolled component' in React. See this page for more details: https://facebook.github.io/react/docs/forms.html
Changing to a controlled component for your scenario means using checked instead of defaultChecked. defaultChecked is meant for uncontrolled components where the values of the input controls (in your case radio buttons) are not controlled by the component's props or render method. React is really designed to use controlled components where props/state/render drive exactly what the UI shows.
The first thing you'll notice when changing from using defaultChecked to checked is that you will no longer be able to change the radio state in the UI. The reason this happens is because although the state of your application is being changed in your saveData function, your component is not being re-rendered.
There are a few different approaches to handling this. The updated jsfiddle is re-rendering your application during saveData. Ideally this should be factored differently. Either the saveData should be moved out of the parent component and into a FormDataStore concept (see React Flux architecture, https://facebook.github.io/flux/docs/overview.html) or the parent component should own the formData state and call setState on itself in saveData which will then cause a re-render of that portion of the DOM tree.

Set focus on a react-bootstrap.Input field using refs.x.getDOMNode.focus

The JSX:
var Button = require("react-bootstrap").Button;
var Input = require("react-bootstrap").Input;
var MyClass = React.createClass({
onclick: function () {
this.refs.email.getDOMNode().focus();
console.log('DOM element', this.refs.email.getDOMNode());
}
render: function () {
return (
<div>
<Button onClick={this.onlick}>Login</Button>
<Input ref='email' type='email' />
</div>
);
},
});
Notes:
The input field is a react-bootstrap.Input class.
The getDOMNode().focus() concept is from Facebook react docs
When the button onclick handler runs, the email input field should be getting the focus, but it's not happening. I found that the react-bootstrap Input field class is rendering the real DOM input field inside a wrapping div. This is the output of the console.log:
<div class="form-group" data-reactid=".1awy8d4e9kw.1.3.$=1$3:0.0.0.1.0">
<input class="form-control" type="email" data-reactid=".1awy8d4e9kw.1.3.$=1$3:0.0.0.1.0.1:$input">
</div>
So looks like the input isn't getting the focus because the focus is applied on the wrapping div, which rejects it (I use document.activeElement in the console to check).
Question is, how to focus on the real input element?
Note:
A bit unrelated but React respects the autoFocus property. That's useful in case the focus is needed to be set immediately after rendering. Still, it's not a substitute for the programmatic way.
Try getInputDOMNode() instead of getDOMNode().
in render
<FormControl
type='text'
ref={(c)=>this.formControlRef=c}
/>
in event handler
someHandler() {
ReactDOM.findDOMNode(this.formControlRef).focus();
}
Since findDOMNode is no longer the suggested method and may be deprecated by Facebook, you should use the autoFocus property (which is provided by the React framework, not react-bootstrap). This is a cross-browser polyfill for the HTML autofocus attribute.
<FormControl
autoFocus
...
/>
Sources:
Focusing to the FormControl
Support needed for focusing inputs in modals?
FormControl provides inputRef attribute
try it,
<FormControl inputRef={ref => { this.input = ref; }} />
https://react-bootstrap.github.io/components/forms/
or you may use vanilla javascript and select the element and set focus
document.getElementById("myInput").focus()

Resources