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.
Related
I actually prefer the design because it allows me to reuse the component easily with react state or redux state. But I am getting this warning. What do you suggest I do?
Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info:
in input (created by InventoryDisplay)
in InventoryDisplay (created by Connect(InventoryDisplay))
in Connect(InventoryDisplay)
in PersistGate
import React from 'react'
import {connect} from 'react-redux'
import {mapStatesToProps, mapDispatchToProps} from '../../redux/maps/maps';
class InventoryDisplay extends React.PureComponent{
constructor(props){
super(props)
this.state={
pagnation: true,
firstPage: null,
currentPage: null,
lastPage:null,
data:null,
limit:null,
}
}
componentDidMount(){
//sets the value of the inventory here
this.props.loadInventory();
}
componentDidUpdate(){
console.log(this.props.CurrentInventory)
this.setState({
currentPage:this.props.CurrentInventory.current_page
})
this.setState({
firstPage: this.props.CurrentInventory.from
})
this.setState({
lastPage: this.props.CurrentInventory.last_page
})
this.setState({ data: this.props.CurrentInventory.data })
this.setState({ total: this.props.CurrentInventory.total })
this.setState({
limit: this.props.CurrentInventory.per_page
})
}
render(){
return(
<label>
<span>Items</span>
<input className={'text-center'} type={'text'} size={2}
value={this.state.limit}/>
</label>
)
}
}
export default connect(mapStatesToProps,mapDispatchToProps)(InventoryDisplay);
<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>
in Provider
That is because, the value of your input is this.state.limit but you're not updating it. When the value of input is changed, the value of state is not changing. You have to give an onChange handler so that whenever the value inside your input changes, the value of the state also changes, rerendering the value inside your input. You can do something like
<input className={'text-center'} type={'text'} size={2} onChange={this.onChangeHandler} value={this.state.limit}/>
and write an onChangeHandler that looks something like
onChangeHandler = (e) => {
setState({limit: e.target.value})
}
Read more about controlled componenets in React docs
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.
I am quite new to React and after going through some tutorials, I was trying the below code of mine.
I made one component, passed props to it from a store, on componentWillMount I make a new state for component. Rendering is fine till now.
Next I bound my state to value of an input box and I have onChange listener as well. Still, I can't change my values in the field.
Since, I am from Angular background, I am assuming binding input's value to state like below will automatically update the property name in state object. Am I wrong here?
componentWillMount(){
this.setState({
updatable : false,
name : this.props.name,
status : this.props.status
});
}
//relevant DOM from component's render function
<input className="form-control" type="text" value={this.state.name} id={'todoName' + this.props.id} onChange={this.onTodoChange.bind(this)}/>
onTodoChange(){
console.log(this);
//consoling 'this' here, shows old values only.
//not sure how and even if I need to update state here.
// Do I need to pass new state to this function from DOM
//TODO: send new data to store
}
My onTodoChange function console the value of this which has same value of state as while initializing. How do I go about changing state by typing in input boxes, so that I can send them to the stores?
Unlike in the case of Angular, in React.js you need to update the state manually. You can do something like this:
<input
id={'todoName' + this.props.id}
className="form-control"
type="text"
value={this.state.name}
onChange={e => this.onTodoChange(e.target.value)}
/>
And then in the function:
onTodoChange(value){
this.setState({
name: value
});
}
Also, you can set the initial state in the constructor of the component:
constructor (props) {
super(props);
this.state = {
updatable: false,
name: props.name,
status: props.status
};
}
You can do shortcut via inline function if you want to simply change the state variable without declaring a new function at top:
<input type="text" onChange={e => this.setState({ text: e.target.value })}/>
With the defaultValue prop in input, you can set a default value like fetch API and put data on input.
<input type="text" defaultValue={value} />
reference: https://reactjs.org/docs/uncontrolled-components.html
I think it is best way for you.
You should add this: this.onTodoChange = this.onTodoChange.bind(this).
And your function has event param(e), and get value:
componentWillMount(){
this.setState({
updatable : false,
name : this.props.name,
status : this.props.status
});
this.onTodoChange = this.onTodoChange.bind(this)
}
<input className="form-control" type="text" value={this.state.name} id={'todoName' + this.props.id} onChange={this.onTodoChange}/>
onTodoChange(e){
const {name, value} = e.target;
this.setState({[name]: value});
}
If you would like to handle multiple inputs with one handler take a look at my approach where I'm using computed property to get value of the input based on it's name.
import React, { useState } from "react";
import "./style.css";
export default function App() {
const [state, setState] = useState({
name: "John Doe",
email: "john.doe#test.com"
});
// We need to spread the previous state and change the one we're targeting, so other data cannot be lost.
const handleChange = e => {
setState(prevState => {
...prevState,
[e.target.name]: e.target.value,
});
};
return (
<div>
<input
type="text"
className="name"
name="name"
value={state.name}
onChange={handleChange}
/>
<input
type="text"
className="email"
name="email"
value={state.email}
onChange={handleChange}
/>
</div>
);
}
In React, the component will re-render (or update) only if the state or the prop changes.
In your case you have to update the state immediately after the change so that the component will re-render with the updates state value.
onTodoChange(event) {
// update the state
this.setState({name: event.target.value});
}
In react, state will not change until you do it by using this.setState({});.
That is why your console message showing old values.
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...
Given a React component with a controlled input, I would like to be able to:
Set the value of the input from the parent's state
Allow the user to change the input to any value
Update the parent's state only after the user submits and input passes validation.
I can accomplish 1 and 2 with the snippet below, but since the value came into the ChildComponent via props, I'm not sure how I can change the input value without changing the value of myInput on the parent.
class ChildComponent extends React.Component
{
render(){
return <input type="text" value={this.props.inputValue} onChange={this.handleChange.bind(this)} />
}
handleChange(e){
this.props.onInputChange(e.target.value);
}
handleSubmit(){
// do some validation here, it it passes...
this.props.handleSubmit();
}
}
class ParentComponent extends React.Component{
constructor(){
super();
this.state = {myInput: ""};
}
render(){
return <ChildComponent inputValue={this.state.myInput} onInputChange={this.handleChange.bind(this)} />
}
handleChange(newValue){
this.setState({myInput: newValue});
}
handleSubmit(){
// do something on the server
}
}
Then you just need to move the state to the child component, instead of rendering from props.inputValue directly. Basically you'd just move handleChange to the child.
Set the initial value from props.inputValue in getInitialState, then make sure to update the child state in componentWillReceiveProps.
componentWillReceiveProps is deprecated
Source: https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
This lifecycle was previously named componentWillReceiveProps. That
name will continue to work until version 17. Use the
rename-unsafe-lifecycles codemod to automatically update your
components.
Use something like this instead:
componentDidUpdate(prevProps) {
if (this.props.yourObj != null && prevProps.yourObj !== this.props.yourObj) {
this.setState({
yourStateObj = this.props.yourObj
});
}
}