I am trying to update a p element in realtime while I type on an input element. The following React code works perfectly. However, if I remove the value attribute completely from the input element, the code STILL works!
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
}
handleInput = (event) => {
this.setState({
input: event.target.value
});
}
render() {
return (
<div>
<input
value={this.state.input}
onChange={this.handleInput} />
<p>Input: {this.state.input}</p>
</div>
);
}
};
So my questions are:
What does the value={this.state.input} line do?
Why does the program still work without that line?
the value={this.state.input} assigns the value of the form to the input box from your component. Your code still works because the event handler still fires when you change the text in the textbox and react doesn't re-render your input. The state of the input value is implicitly in the state of the DOM but this state isn't from your component.
If you had TWO inputs that used the same state, then when you type, the second input won't update. This is where you'll see something different from expected because you omitted the value={this.state.input}. If you include that in both input values then your text boxes will mirror each other.
class ControlledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
input: ''
};
}
handleInput = (event) => {
this.setState({
input: event.target.value
});
}
render() {
return (
<div>
<input
onChange={this.handleInput} />
<p>Input: {this.state.input}</p>
<input
onChange={this.handleInput} />
<p>Input: {this.state.input}</p>
</div>
);
}
};
in the code above, one of the input's won't update when both should have the same state :)
What does the value={this.state.input} line do?
This line sets the value attribute from the input using your state variable input, in your case it will initialise it with '' (nothing) at the start (i.e., this is equivalent to <input value="" ...>)
If you change your this.state.input to something like
this.state = {
input: 'foo'
};
you should see how the input value changes to foo upon the start of your program.
Why does the program still work without that line?
Because you are already setting it to be empty at the start so it doesn't actually change anything in your program. Your onChange event handler still fires.
Related
I cannot understand why we set the value={this.state.task} when it is just an empty string, and how exactly the flow of data goes from the input value and then to the state.
When we first set the value, it's basically an empty string. But when I try to actually set value='' , the input field does not function properly on the rendered page.
I get that onChange we set the state to the corresponding name and value, and that that's how the data is flowing into the state. But then why does it not work when, again, we just set value='' ?
import React, { Component } from 'react';
import uuid from 'uuid/v4';
export class NewTodoForm extends Component {
constructor(props) {
super(props)
this.state = {
task: ""
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit(e){
e.preventDefault();
this.props.createTodo({ ...this.state, id: uuid() });
this.setState({ task: "" });
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor='task'>New Todo</label>
<input
type='text'
placeholder='New Todo'
id='task'
name='task'
// why is this {this,state.task} ?
value={this.state.task}
onChange={this.handleChange}
/>
<button>Add Todo</button>
</form>
</div>
)
}
}
export default NewTodoForm
Because value is setting ... well the value of the input. By doing this value={this.state.task} basically you are connecting your input with the component's state and with the lifecycle of the React component. So basically whenever you change your component's state that has the input from anywhere (even programmatically), React will be able to update the input correctly and there won't be any bugs or weird stuff happening.
In the React docs it is explained very well. They are doing this controlled component ...
An input form element whose value is controlled by React in this way is called a “controlled component”.
... so that the state of the React component to be the only 'source of truth', meaning to prevent weird bugs and undesired behaviour.
Since the value attribute is set on our form element, the displayed value will always be this.state.value, making the React state the source of truth.
It is always a good practice to have one source of truth and not many. In this case if you leave the input's value to be different from the component's state, you are making more than one source of truths and thus exposing your app to bugs.
Instead of re-rendering the entire component-tree whenever "<InputText style{...}>" is changed, I am trying to use refs in my Class Component. (I am using React Native with Expo managed workflow.)
Using refs, the typed text appears as it should in the InputText field.
But, when a button is pressed, the value of the typed text (value of the InputText) should be console logged, however it is not.
export class Feed extends Component {
constructor(props){
super(props);
this.state = {
//some state variables
}
this.myTextFromInput = React.createRef()
}
I started by creating the myTextFromInput ref (above).
<TextInput
style={{height:100}}
ref={this.alias}
placeholder="Input Text Here"
/>
I then used the myTextFromInput ref in the InputText component. And lastly, the button!
<Button onPress={()=>console.log(this.myTextFromInput.current.value)}>Press me!</Button>
This gives me undefined. I have also tried this.myTextFromInput.value and a .getText() method which is outdated.
How can I obtain the inputed text?
UPDATE:
Terminal log undefined. But snack works fine!?
You aren't passing the correct reference to TextInput, it should be this.myTextFromInput not this.alias, take a look:
export class Feed extends Component {
constructor(props){
super(props);
this.state = {
//some state variables
}
this.myTextFromInput = React.createRef()
// bind the method to the component instance
this.logInputText = this.logInputText.bind(this)
}
logInputText() {
console.log(this.myTextFromInput.current.value)
}
render() {
return (
<View>
<TextInput
style={{height:100}}
// pass the correct reference here
ref={this.myTextFromInput}
placeholder="Input Text Here"
/>
<Button
onPress={this.logInputText}>
Press me!
</Button>
</View>
)
}
}
Also don't forget that whether you use a method instead of arrow function you've to bind it to the class instance, like I did. See this question and this example from react docs.
Update: React.ref on Android and iOS doesn't seems to work as the same way as it works on web, you can't get the value from input because the component doesn't provide this property, doing a console.log(this.myTextFromInput.current) you can see all the available properties. One solution from this question is to use TextInput from the package react-native-paper, as it provides the input value from the ref, or you could use the common state approach to store the input value, like so:
export class Feed extends Component {
constructor(props){
super(props);
this.state = {
myTextFromInput: ""
}
// bind the method to the component instance
this.logInputText = this.logInputText.bind(this)
}
logInputText() {
console.log(this.state.myTextFromInput)
}
render() {
return (
<View>
<TextInput
style={{height:100}}
// you don't need to use ref
placeholder="Input Text Here"
onChangeText={(text) => this.setState({myTextFromInput: text})}
/>
<Button
onPress={this.logInputText}>
Press me!
</Button>
</View>
)
}
}
I have a state called word which is originally set to visitor. The page has an H1 that says "Hello, {this.state.word}". There is an input on the page that changes the state of word to the inputted value. What I want to happen is that when the user has changed the state of Word using the input, to be able to set the state back to it's original word which was visitor simply by hitting backspace until the pages input is empty.
I've tried using conditional logic inside the onChange() function to say that if the word state equals an empty string, set the state of word to visitor. This isn't working for some reason that I haven't been able to figure out.
import React, {Component} from 'react';
import "./Visitor.css";
class Visitor extends Component{
constructor(props){
super(props);
this.state={
word: 'visitor'
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
if(this.state.word === ''){
this.setState({
word: 'vis'
});
}
this.setState({
[e.target.name]: e.target.value
});
}
render(){
return(
<div className="Visitor">
<h1>Hello, {this.state.word}</h1>
<input type="text"
onChange={this.handleChange}
name="word"
placeholder="Type your name here"/>
<button>Clear</button>
</div>
)
}
}
export default Visitor;
No error messages with my tried and failed if statement, just no correct result. The result should be that at any point the input on the page is empty, the state of "word" should be "visitor".
In handleChange just make this.state.word to e.target.value==="" and also return inside the if statement because once if statement change the state again it comes down below and changes state to empty string. use below code it would help you something like this
handleChange(e){
if(e.target.value ===''){
return this.setState({
word: 'vis'
});
}
this.setState({
[e.target.name]: e.target.value
});
}
Looks like you want to check e.target.value === '' instead of this.state.word in handleChange
Edit : Also you need to avoid overwriting your first setState with the second one, so you final handleChange should look something like this
handleChange = e => {
if (e.target.value === '') {
// handle if empty
this.setState({ word: 'vis' });
} else {
// business as usual
this.setState({ word: e.target.value });
}
};
I have a react component that consists of an input field and a button. When the button is clicked I want to run an update function that is also inherited from the parent controller. In the react documentation they have an onchange handler attached to this input and they get the new value of the input with the onchange event object. However in my case I get an event object describing the button, not the input field. What is the correct way to access the new input field value from handle click?
class QuoteButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
this.props.onQuoteUpdate(//what should go here?)
}
render() {
const cost = this.props.cost;
return (
<div>
<Input value={cost}/>
<Button basic color='green' onClick={this.handleClick}>Submit Quote</Button>
</div>
);
}
}
If you a going to change input value in this component you need to use react states (if you don't use state management libraries such as mobx or redux). In most cases input have to be a controlled component.
After component was mounted add cost value to states. You also need appropriate handler for input.
P.S. You could use arrow functions to avoid binding handlers in constructor.
class QuoteButton extends React.Component {
constructor(props) {
super(props);
this.state = { inputValue: '' };
}
componentDidMount() {
this.setState({inputValue: this.props.cost});
}
handleClick = () => {
this.props.onQuoteUpdate(this.state.inputValue);
}
handleInputChange = event => {
this.setState({inputValue: event.target.value});
}
render() {
return (
<div>
<Input value={this.state.inputValue} onChange={this.handleInputChange} />
<Button basic color='green' onClick={this.handleClick}>Submit Quote</Button>
</div>
);
}
}
Hope it helps
Here is my editing component:
class EditField extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
edit(e) {
this.setState({ value: e.target.value });
if (e.keyCode === 13) {
this.props.onEdited(this.state.value);
}
}
render() {
return (
<div>
<input
type="text"
value={this.state.value}
onChange={this.edit.bind(this)}
/>
</div>
)
}
}
I need to populate state from props like this:
function Container({ entity, onEdited }) {
return (
<div>
<EditField onEdited={onEdited} value={entity.firstName} />
<EditField onEdited={onEdited} value={entity.lastName} />
</div>
);
}
The Container component get onEdited and entity props from redux store.
Container's parent will handle data fetching and onEdited (which will
only be triggered if user hit Enter) will dispatch request to the server.
My problem is how to initialize value props properly? Because if I use:
componentDidMount() {
this.setState({
value: this.props.value
});
}
I got empty state because fetching data is not finished when componentDidMount
called. And if I use:
componentWillReceiveProps(nextProps) {
this.setState({
value: nextProps.value
});
}
I got this warning:
Warning: EditField is changing a controlled input of type text to be
unncontrolled. 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.
So, how to do this correctly?
This is what I recommend:
You could use getInitialState from EditField to populate the value state from the value prop. But this won't work, because getInitialState will only be called once, so subsequent renders will not update the state. Besides, this is an anti-pattern.
You should make the EditField component controlled. Always pass the current value as prop and stop dealing with state at all. If you want a library to help you link the input state with Redux, please take a look at Redux-Form.
The onEdited event you created, at least the way you did it, doesn't play well with controlled inputs, so, what you want to do is to have an onChange event that is always fired with the new value, so the Redux state will always change. You may have another event triggered when the user hits enter (e.g onEnterPressed), so you can call the server and update the entity values. Again. Redux-Form can help here.
Apparently entity.firstName and entity.lastName can only contain the values that the user has confirmed (hit enter), not temporary values. If this is the case, try to separate the state of the form from the state of the entity. The state of the form can be controlled by Redux-Form. When the user hits enter, you can trigger an action that actually calls the server and updates the state of the entity. You can even have a "loading" state so your form is disabled while you're calling the server.
Since Container subscribes to Redux store, I suggest make the EditField stateless functional component. Here's my approach:
const EditField = ({
onEdited,
value
}) => (
<div>
<input
type="text"
value={value}
onChange={onEdited}
/>
</div>
);
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
edit = (e) => {
this.setState({value: e.target.value});
e.keyCode === 13 ? this.props.onEdited(this.state.value) : null;
};
sendValue = (val) => val ? val : this.state.value;
render() {
this.props = {
firstName: "Ilan",
lastName: null
}
let { firstName, lastName, onEdited } = this.props;
return (
<div>
<EditField onEdited={this.edit} value={this.sendValue(firstName)} />
<EditField onEdited={this.edit} value={this.sendValue(lastName)} />
</div>
)
}
}
ReactDOM.render(<Container />, document.getElementById('app'));
A live demo: https://codepen.io/ilanus/pen/yJQNNk
Container will send either firstName, lastName or the default state...