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
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.
I am facing issues while updating the values. Initially I am taking the values from the parent class to put into the text box, and then if I want to update the values into the form through the child component it should basically set the state in child component and pass the updated values to the API. But now when I try to change the values in the text box, it only changes one character and doesn't keep track of the state of all the props. How can I solve this? I have tried using the defaultValue it does change the values but it cannot keep track of the state change.
PS: The updateToApi is just a sample function that is using post to update values into the api
my sample project is here
https://codesandbox.io/s/sad-perlman-ukb68?file=/src/parent.js
#class Parent#
import React from "react";
import "./styles.css";
import Child from "./child";
class Parent extends React.Component {
constructor() {
super();
this.state = {
data: {
username: ["mar"],
name: [null]
}
};
}
updateToApi(data) {
var username: data.username;
var name: data.name;
}
render() {
return (
<Child data={this.state.data} updateToApi={this.updateToApi.bind(this)} />
);
}
}
export default Parent;
##class Child##
import React from "react";
import "./styles.css";
import { Button } from "react-bootstrap";
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
name: ""
};
}
handleSubmit = e => {
e.preventDefault();
};
handleChange = e => {
const data = { ...this.state };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={
this.props.data.username !== "undefined"
? this.props.data.username
: this.state.username
}
onChange={this.handleChange}
/>
</label>
<b />
<label>
Name:
<input
type="text"
name="Name"
value={
this.props.data.name !== "undefined"
? this.props.data.name
: this.state.name
}
onChange={this.handleChange}
/>
</label>
<br />
<Button variant="primary" onClick={this.props.updateToApi} />
</form>
</>
);
}
}
export default Child;
Why do you have 2 separate states? You should get rid of the state in your Child component entirely and only work with the Parent's state. Put HandleChange function in your Parent component also and pass it down through props.
UPD
Well, if you want for changes in your inputs to be visible, you could change the onchange handler in your Child coponent to
handleChange = e => {
this.setState({
[e.currentTarget.name] : e.currentTarget.value });
};
and the Input value just to this.state.username
Though i'm still having hard time to grasp what you are trying to accomplish here. Having 2 separate conditional states for the input fields is just too complicated. Imagine if your app would be a bit more complex? You'd lost yourself to debugging this stuff:
value={ this.props.data.username !== "undefined"
? this.state.username
: this.state.username
}
So here i highly recommend you to reevaluate all your data strucuture and data flow within the app. You should have the least amount of sources of truth within your app. Ideally one. So just use the main state in the Parent component and pass down the props that are required.
import React from 'react'
import Input as InputAnt from 'antd'
class myInput extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentDidMount() {
this.inputRef.current.focus();
}
render() {
return (
<InputAnt ref={this.inputRef}/>
)
}
}
export default myInput;
Here the InputAnt does receive focus, but my concern is that : the 'myInput' component can be used by many other components to render an input along with so many other components. So maybe I don't want the input to get focus in all the cases wherever 'myInput' component is used, I only want to focus under particular scenarios, how can that be achieved?
Just pass an additional prop when you are using myinput.
<myInput shouldFocus />
and in the componentDidMount lifecycle hook:
componentDidMount() {
if (this.props.shouldFocus) {
this.inputRef.current.focus();
}
}
and when you want to use myInput but don't want to focus your input just use it as:
<myInput />
I have a Checkbox component which is simple checkbox under the hood however styled to my needs, as it has custom 'fake element' which acts as original one.
Checkbox
import React, {Component} from 'react';
import FormGroup from 'react-bootstrap/lib/FormGroup';
import '../../../stylesheets/common/forms/Checkbox.scss';
export default class extends Component {
constructor(props) {
super(props);
this.state = {checked: props.checked};
}
toggle() {
const checked = !this.state.checked;
this.setState({checked});
}
/**
* custom prop indicates that we want create custom element to toggle checkbox ie. ImageCheckbox
*
*/
render() {
const {id, className, children, name, custom = false} = this.props;
const toggle = this.toggle.bind(this);
return (
<FormGroup className={`Checkbox ${className}`}>
<input id={id}
name={name}
type="checkbox"
checked={this.state.checked}
onChange={toggle}/>
{!custom && <div className="fake-checkbox" onClick={toggle}/>}
<label htmlFor={id}>{ children }</label>
</FormGroup>
)
}
}
As comment says it in code component allows to create a custom 'fake' element inside label to act as toggle for checkbox.
ImageCheckbox:
import React from 'react';
import Checkbox from '../../common/forms/Checkbox';
import '../../../stylesheets/common/forms/ImageCheckbox.scss';
export default props => (
<Checkbox className="ImageCheckbox" {...props} custom={true}>
<div className="image"
style={{backgroundImage: 'url(' + props.href + ')'}}>
<p>{ props.children }</p>
</div>
</Checkbox>
);
It works how I want but when i click ImageCheckbox I get warning in browser:
warning.js:36 Warning: _class is changing an uncontrolled input of type checkbox 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: https://facebook.github.io/react/docs/forms.html#controlled-components
Im not sure what I do wrong and should I be concerned about it.
I found answer in Adam's reply React - changing an uncontrolled input.
I've change undefined to false in constructor when prop isn't passed:
constructor(props) {
super(props);
const {checked = false} = props;
this.state = {checked};
}
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...