React: why binding value makes input readonly? - reactjs

given this initial state:
this.state = {
person: { id: 1, name: "Liero" }
};
when I bind input like this: <input value={this.state.person.name} />
Then I makes the input non editable. I understand that it would make state and DOM element out of sync.
However, when I use defaultValue: <input defaultValue={this.state.person.name} />,
I loose posibility to change the person's name from code (e.g. when I want to reset the form).
When I manually sync the state and input value:
<input value={this.state.person.name}
onChange={(evt) => this.state.person.name = evt.target.value }/>
it does not work until I call setState, or force refresh?
Do I really need to force render of entire component each time the input value changes, when I want to have the posibility to control the person.name from code?

Instead of set this.state.person.name directly. call this.setState.
This will trigger another render cycle and then bind this.state.person.name to value:
<input value={this.state.person.name}
onChange={(evt) => {
this.state.person.name = env.target.value;
this.setState({person:this.state.person});
}}/>

Did you mean to setState() instead of mutate the state? like this:
class MyComponent extends Component {
state = { person: { id: 1, name: "Liero" } }
updateName = (e) => {
this.setState({
person: {...this.state.person, name: e.target.value}
})
}
render (){
return (
<input type="text" onChange={this.updateName} value={this.state.person.name} />
);
}
}

Related

React wrong & right practices with form elements

Hyall
Can you please point out bad practices / mistakes in the code below?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "default title"
};
this.inputTxt = this.state.title;
this.myRef = React.createRef();
}
componentDidMount() {
this.myRef.current.value = this.inputTxt;
}
handleSubmit = e => {
e.preventDefault();
console.log("submitted");
this.setState({ ...this.state, title: this.inputTxt });
};
handleInput = e => {
this.inputTxt = e.target.value;
};
render() {
return (
<>
<div>{this.state.title}</div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
onChange={this.handleInput}
ref={this.myRef}
></input>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
</>
);
}
}
And some special questions:
is it ok to use this.somevar properties of component class to store variables' values? how to avoid naming collisions?
is it normal to use refs to set input's value?
if I want to set onChange and value bound to reactive variable in one input control, it will freeze? how to gain [(ngModel)] Angular-like control over input element?
It seems like you're over complicating things. I don't see a need for refs here. I don't think setting a class property will trigger a re-render, so this way of managing input might not work at all regardless of it not being a best practice.
Just use state as the value, and update state on change. To keep things flexible, use the input's name as the state key. Something like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "default title"
};
}
handleSubmit = e => {
e.preventDefault();
console.log("submitted");
// Not sure if thats what you're looking for..
// Also: no need to do {...this.state, }. setState does a merge, not overwrite
this.setState({ title: this.state.input1 });
};
handleChange = e => {
// Use e.target.name as the computed property name,
// so it can be used for infinite number of inputs
this.setState({[e.target.name]: e.target.value});
};
render() {
return (
<>
<div>{this.state.title}</div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="input1" // Give it a unique name for setting state
value={this.state.input1} // Specify the value instead of using a ref
onChange={this.handleChange}
></input>
<button type="submit">Save</button>
<button type="reset">Reset</button>
</form>
</>
);
}
}
Here is the link to the react docs on refs.
The primary they recommend use-cases are:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
Which I don't believe apply, here. So I wouldn't recommend using them here.

Is there any public react function that gets executed every time state is changed just like render function?

I have a child component. It should create an object from props and render it. This object should get added as a state.
Below is the current code.
Example:-
<popupComponent element={object} />
popupComponent.js
class popupComponent extends Component {constructor(props) {
super(props);
this.state = {
name: ""
}
}
updateName (event) {
this.setState({
name: event.currentTarget.value
})
}
publishElement () {
this.props.saveAndClose({
name: this.state.name
});
this.setState({
name: ""
})
}
render() {
return (
<div draggable="true" >
<h4>Name:</h4>
<input id="elementName" type="text" placeholder="Enter element name" value={element.name} onChange={this.updateName.bind(this)}/>
<button id="saveAndClose" onClick={this.publishElement.bind(this)}>Save & close</button>
</div>
);
}
}
export default popupComponent;
Question: Which function other than render gets executed whenever state is changed? In this scenario constructor runs only once and I cannot try that because the time constructor gets executed, state isnt available.
Resolved issue by conditionally not creating the component at all.
Actual issue, Somehow this component's constructor was getting called only once but I wanted it getting called whenever it gets visually shown.
Resolved issue by conditionally not including the component at all as below.
{this.state.show ? <PopupMarkupEditor
element = {selectedElement}
saveAndClose = {this.saveElement}
show = {this.state.show}
/> : null }

React Child List Component

I am learning react, what is the best practice for the following scenario?
(Note just typed this up - not perfect, just meant to illustrate what I'm trying to do). Given this data -
const person = {
name: "",
tasks: [
{name: "", done: false }
]
}
I want a form to edit both the name and the tasks at the same time - add, delete and edit the fields of the tasks.
What I was thinking:
<PersonForm>
<PersonName />
<TaskList />
</PersonForm>
The name can be easily edited by the example given by react documentation:
class PersonForm extends React.Component {
constructor(props) {
this.state = {
name: "",
tasks: [
{name: "", done: false }
]
};
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({ [name]: value });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<PersonName name={this.state.name} onChange={this.handleInputChange} />
<TaskList tasks={this.state.tasks}
deleteTask={this.deleteTask}
addTask={this.addTask}
updateTask={this.updateTask}/>
<input type="submit" value="Submit" />
</form>
);
}
}
class PersonName extends
render() {
return (
<label>
Name:
<input type="text" name="name" value={this.props.value} onChange={this.props.onChange} />
</label>
)
}
}
I know the recommendation is lifting state up. So I could put the addTask, removeTask and updateTask callback methods in PersonForm.
class PersonForm extends React.Component {
. . .
addTask = event => {
this.setState(prev => ({ tasks: [...prev, {name: "", done: false}]}));
}
removeTask = key => {
this.setState(prev => ({ tasks: prev.filter(t => t.key !== key) });
}
updateTask = ???...
. . .
But,
It seems to me the best way to encapsulate functionality would be for the addTask, deleteTask, updateTask functionality to be in the TaskList component. Am I wrong?
It seems like otherwise PersonForm would get huge (in a real world example). Would this mean TaskList would need state?
Basically,
What is the best practice for this sub-list scenario?
If callbacks from the top are the answer, how to update the task data?
Moving some logic outside of the component when it starts getting huge makes sense but at the same time it's convenient to keep all form state in one place. You could use ref on child component to retrieve it's state but it's an ugly solution and considered a bad practice. I believe in my experience I haven't encountered a very huge forms so even they were big components it was pretty fine to read/manage all the state there. But if you'd really want to move some logic out of it I think you could, for example, use the new context API to store the form's state (or just tasks list's state) and subscribe to it in PersonForm component (to read) and in TaskList (to read and change).

How come I can't type anything into my React search box?

I have a component called SearchInput and it has in it
<input
type='text'
className='input-field'
value={value}
placeholder={this.state.placeholder}
// onFocus={::this.onFocus}
// onBlur={::this.onBlur}
// onKeyUp={::this.onKeyUp}
// onKeyDown={::this.hanleArrowKeys}
// onChange={::this.onChange}
/>
However, when I type anything into it, nothing happens. The text doesn't even appear. What am I doing wrong?
Probably your state is not updating, maybe didn't you bind "this" to function onChange?
Here is an example of correct input in react:
class SearchInput extends Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
this.setState({ value: event.target.value });
}
render() {
return <input type="text" value={this.state.value} onChange={this.handleInputChange}/>;
}
}
Probably you are binding the value of the input box with an attribute in the state of the component and you are either not providing an onChange prop to the input tag or your onChange callback method is not updating the attribute in the state to which the input tag's value is bound.

How to allow updates to input without updating props in React?

I would like to allow a user to enter changes in an input field without propagating them to the parent. I did this by returning out of the onChange function whenever I don't want to propagate. However this seems to undo the character I typed.
Here is a use case. I have a number field. I want to trigger onChange on parent when there is a number entered, but ignore "." and ","s (formatters.staticToFloat removes them).
export default class NumberField extends React.Component {
render () {
var props = this.props;
var format = props.formatter || formatters.number;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={!_.isUndefined(props.value) ? format(props.value) : null}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
return;
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
So far the best way I've come up with is maintaining a separate formattedValue state that only gets set when I want to override the default formatting. It works, but seems like a super dirty solution.
export default class NumberField extends React.Component {
constructor () {
super();
this.state = {
formattedValue: null
};
}
render () {
var props = this.props;
var state = this.state;
var format = props.formatter || formatters.number;
var inputValue = state.formattedValue || (
!_.isUndefined(props.value) ?
format(props.value) :
null
);
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={inputValue}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
this.setState({
formattedValue: e.target.value
});
} else {
this.setState({
formattedValue: null
});
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
Sounds like a case for using state to store the formatted value inside the component, and use a special variant of setState with a callback.
all user input (including . and ,) is put in state and rendered in input field back to user.
only when the format is correct, the parent onChange() is called.
The parent may actually do a re-render triggered by the onChange() call. Therefore, we need to make sure that the last entered character is updated in state, and only after that the parent's onChange() will be called.
Your component would look like as follows:
export default class NumberField extends React.Component {
constructor (props) {
super(props);
this.state = {
value: !_.isUndefined(props.value) ?
props.formatter ? props.formatter(props.value).toString() : formatters.number(props.value).toString()
: null;
};
}
render () {
var props = this.props;
var state = this.state;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={state.value}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
// if numValue is different from current state
// then it must be an OK update,
// so we update state AND call parent if function exists
if (numValue.toString() != this.state.value) {
this.setState({
value: numValue
},
this.callParent // here is the magic: we pass a callback, to be called after state update and after re-render
);
} else {
// otherwise we only update state (to display invalid character)
this.setState({
value: numValue
});
}
}
callParent() {
// state is updated and component has re-rendered when this is called
// so we can use state.value to inform parent
if (this.props.onChange) {
this.props.onChange({
value: this.state.value,
valid: validation.isValid(this.state.value, this.props.validation)
});
}
}
};
This may be a bit of overkill: you keep a state (formatted value) that you also communicate to the parent. If your parent ALWAYS passes down the newly forwarded input, then you could make your component a lot simpler:
No state, but simply re-render based on props.
You only really need state if the value presented to the user in the input field can deviate from what you communicate to parent.

Resources