Input onChange() event is not called - javascript

I'm rendering an Input of type='number'.
The Input has the value of this.state.value.
The Input and all the UI Components are generated via Semantic-UI, but I think that's not of a significant importance info.
I also have a custom arrow menu for this input instead of the original one. [input of type number has two arrows to decrease/increase the value]
Render()
render() {
// Custom Menu
const arrowsMenu = (
<Menu compact size='tiny'>
<Menu.Item as='a' onClick={ this.decreaseNumber.bind(this) }>
<Icon name='chevron left' size='small' />
</Menu.Item>
<Menu.Item as='a' onClick={ this.increaseNumber.bind(this) }>
<Icon name='chevron right' size='small' />
</Menu.Item>
</Menu>
);
return (
<Input value={ this.state.value } type="number" label={ arrowsMenu } placeholder="Raplece ma" onChange={ this.onChange.bind(this) } />
);
}
The Custom Menu uses these two functions:
decreaseNumber(e) {
this.setState({
value: this.state.value - 1
});
}
increaseNumber(e) {
this.setState({
value: this.state.value + 1
});
}
onChange
You can place anything.
onChange(e) {
console.log('====================================');
console.log('Hello pals');
console.log('====================================');
}
The problem is
That whenever I push an Arrow from the Menu, the onChange() event of the Input is not triggered. But the value of the input is changed.
(Of course, because the this.state.value variable is changed in the state)
If I do the same with the original arrows, of course, the value is changed as it should.
Why is that and how can I fix it?

onChange is only called if the user goes into the Input component and interacts with it to change the value (e.g. if they type in a new value). onChange is not called if you change the value programmatically through some other avenue (in your example changing it via the custom menu).
This is working as intended design.
If you want to trigger onChange, then call it from your increaseNumber and decreaseNumber methods.

You can call onChange with any code you want, but if you want to reflect the new value you need to set the state according to the new input value from the event.
As for decreaseNumber and increaseNumber you need to change the state as well but here you are doing calculation so you need to make sure it's a number (or convert it to a number) because you are getting a string back from the event.
Working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
onChange = e => this.setState({value: e.target.value});
increaseNumber = () => this.setState({value: Number(this.state.value) + 1});
decreaseNumber = () => this.setState({ value: Number(this.state.value) - 1 });
render() {
const { value } = this.state;
return (
<div>
<button onClick={this.decreaseNumber}>-</button>
<input type="number" value={value} onChange={this.onChange}/>
<button onClick={this.increaseNumber}>+</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>
Edit
For triggering the onChange handler just call this.onChange but note that you can't pass the event like the native event handler does but you can pass a simple object that mimic the normal event object with a target.value.
Another option is to try triggering it via a ref but keep in mind it can cause an infinite loop in some cases.
Edited Example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
onChange = (e) => {
this.setState({ value: e.target.value });
console.log('change', e.target.value);
}
increaseNumber = () => {
const { value } = this.state;
const nextValue = Number(value) + 1;
const changeEvent = {
target: {
value: nextValue
}
};
this.onChange(changeEvent);
}
decreaseNumber = () => {
const { value } = this.state;
const nextValue = Number(value) - 1;
const changeEvent = {
target: {
value: nextValue
}
};
this.onChange(changeEvent);
}
render() {
const { value } = this.state;
return (
<div>
<button onClick={this.decreaseNumber}>-</button>
<input type="number" value={value} onChange={this.onChange} />
<button onClick={this.increaseNumber}>+</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>
Edit #2
As a followup to your comment:
list's value is its content/children. If some of the children change,
then list changes with them as well
Well, this has an easy solution, you can use the ref (like i mentioned in the first section of my answer) and dispatch an event with bubbles:true so it will bubble all the way up to the parents.
Using your new example code:
class App extends React.Component {
liOnChange(e) {
console.log('listed item/items changed');
}
inputOnChange(e) {
console.log('input changed');
}
handleClick(e){
var event = new Event("input", { bubbles: true });
this.myInput.dispatchEvent(event);
}
render() {
return(
<div>
<ul>
<li onChange={this.liOnChange.bind(this)}>
<input ref={ref => this.myInput = ref} type='text'onChange={this.inputOnChange.bind(this)}/>
<button onClick={this.handleClick.bind(this)}>+</button>
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>
I in general don't like using refs but sometimes you need them.

Related

onChange handler for Object.keys().map in React to update object properties

I have a component with an empty metadata object at DOM load, the server sends data to fill the empty metadata object with properties that will be assigned values within the form. I am able to iterate through the meta data and see multiple input fields correctly labeled yet when I got to input something it either doesn't change anything and the console logs the single keystroke or it returns TypeError: Cannot read property 'handleChange' of undefined. The title field handles the change just fine.
My code:
class Item extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
metadata: {}
}
}
componentDidMount() {
... //retrieve metadata from server
this.setState({
metadata: metadata
});
console.log(metadata); //{meta1: "", meta2: "", meta3: "", meta4: "", meta5: "", …}
}
handleChange = (field) => {
return (value) => this.setState({ [field]: value });
}
render() {
const {
title,
metafield
} = this.state;
}
return(
//code to start form
<TextField value={title} onChange={this.handleChange(title)} label="Title" type=text />
{Object.keys(metadata).map(function(key) {
return (
<TextField key={key} value={metadata[key]} onChange={this.handleChange({key})} label={key} type=text />
)
})}
//code to end form
)
}
I'm sure it's because the handleChange isn't equipped to handle changes on object properties but I'm not sure how to access that layer. I've tried binding a handleMetadataChange function on the constructor and use e.target to assign the values but the failing behavior persists.
There are a couple of bugs:
handleChange sets state like this: this.setState({ [field]: value}); but the values are in state.metadata not in state.
In render
you get metafield from state but initially you set metadata
and in handleChange you don't use any of it.
You always re create onChange for TextField even if nothing has changed, this causes needless DOM re renders.
Here is a working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
metadata: {},
};
}
componentDidMount() {
Promise.resolve().then(() =>
this.setState({
metadata: { x: 'x', y: 'y' },
})
);
}
handleChange = (field, value) =>
//you forgot you are setting metadata of state
this.setState({
...this.state,
metadata: { ...this.state.metadata, [field]: value },
});
render() {
const {
metadata, //you used metaField here but it's metadata
} = this.state;
return (
<div>
{Object.keys(metadata).map(key => (
<TextField
key={key}
value={metadata[key]}
onChange={this.handleChange} //always pass the same handler function
changeKey={key} //added for optimization
label={key}
/>
))}
</div>
);
}
}
//make textfield a pure component as it only receives props
// You could call this TextFieldContainer and not change TextField at all
const TextField = React.memo(function TextField({
value,
onChange,
changeKey,
label,
}) {
const rendered = React.useRef(0);
rendered.current++;
return (
<div>
times rendered: {rendered.current}
<label>
{label}
<input
type="text"
value={value}
onChange={e =>
onChange(changeKey, e.target.value)
}
/>
</label>
</div>
);
});
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Insert this at the end of your constructor: this.handleChange = this.handleChange .bind(this);
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
This is not React-specific behavior; it is a part of how functions work in JavaScript. Generally, if you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method
Handling Events
class Item extends React.Component{
constructor(props) {
super(props);
this.state = {
title: '',
metadata: {}
}
}
componentDidMount() {
... //retrieve metadata from server
this.setState({
metadata: metadata
});
console.log(metadata); //{meta1: "", meta2: "", meta3: "", meta4: "", meta5: "", …}
}
handleChange = (field,e) => {
let temp = this.state.metdata;
temp[field] = e.target.value;
this.setState({metadata: temp });
}
render() {
const {
title,
metafield
} = this.state;
}
return(
//code to start form
<TextField value={title} onChange={this.handleChange(title)} label="Title" type=text />
{Object.keys(metadata).map(function(key) {
return (
<TextField key={key} value={metadata[key]} onChange={(e)=>this.handleChange(e,key)} label={key} type=text />
)
})}
//code to end form
)
}

React JS Nested elements both use same onClick event and child event is not working

I have a todos list that I am working on. I want to be able to mark the task complete by clicking the checkbox or anywhere in the div of that todo. However, I get a warning when I only have the onClick event on the parent component
Here is the component code that works but gives me a warning:
render(){
const {todo, handleClick} = this.props;
const className = this.getClassName(todo.complete)
return (
<div
className={className}
onClick={handleClick}
>
<input
type="checkbox"
className="todo-checkbox"
checked={todo.complete}
/>
<span
className='todo-text'>
{todo.text}
</span>
</div>
)
}
}
and here is the warning:
index.js:1375 Warning: Failed prop type: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
To fix this I used the suggested e.stopPropagation(); and added an onChange event to the child element. However, now only the parent div is working, so I can change successfully mark a todo anywhere in the div except for the checkbox. I think this is because they share the same method that it's not separating them as two different events.
stopBubbling = (e) => {
e.stopPropagation();
}
handleChange = (e, key) => {
this.stopBubbling(e)
this.setCompletebyId(e, key)
}
setCompletebyId = (e, key) => {
const { todos } = this.state;
const index = key - 1;
const complete = todos[index].complete;
todos[index].complete = !complete;
this.setState({
todos
})
}
Any help is appreciated!
Have you tried putting the onClick in your first example on the input itself and not the div?
From React's perspective, it assumes the input is "uncontrolled" and the user cannot interact with it. So the warning is providing options if that is the case. But in this scenario, you want it to be controlled. It was working because the click event on the input checkbox would bubble up to the div and still invoke the click handler.
class Checkbox extends React.Component {
render() {
const { todo, handleClick } = this.props;
return (
<label>
<input
type="checkbox"
className="todo-checkbox"
onClick={handleClick}
checked={todo.complete}
/>
{todo.text}
</label>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todo: { complete: false, text: "TODO" }
};
}
handleClick = () => {
this.setState({ todo: { ...this.state.todo, complete: !this.state.todo.complete } });
};
render() {
const { todo } = this.state;
return <Checkbox todo={todo} handleClick={this.handleClick} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I think putting
onChange = {handleClick}
inside the input tag might help as a checkbox expects an onChange function.

ReactJS - Checkbox onChange event not firing

I'm trying to implement a fields set of checkboxes in React rendered from an object as follows:
constructor() {
this.state.todo = {
eat: true,
sleep: false,
react: true
}
this.toggleCheckbox = this.toggleCheckbox.bind(this);
}
toggleCheckbox(e){
console.log(e); // nothing :-/
}
render() {
return (
<div>
{ Object.keys(this.state.todo).map((val, i) => (
<div key={i} >
<input
type="checkbox"
value={val}
onChange={this.toggleCheckbox}
checked={this.state.todo[val]}
/><label>{val}</label>
</div>
))}
</div>
)
}
Everything renders correctly but I am not able change any of the checkboxes. console logging the toggleCheck() event is not being triggered.
Ive tried using onClick vs onChange which has no effect.
You are getting the keys from this.state.tables, but your state is called this.state.todo.
You can use each value as name instead and toggle the relevant todo state property with that.
Example
class App extends React.Component {
state = {
todo: {
eat: true,
sleep: false,
react: true
}
};
toggleCheckbox = e => {
const { name } = e.target;
this.setState(prevState => ({
todo: {
...prevState.todo,
[name]: !prevState.todo[name]
}
}));
};
render() {
return (
<div>
{Object.keys(this.state.todo).map((val, i) => (
<div key={i}>
<input
type="checkbox"
name={val}
onChange={this.toggleCheckbox}
checked={this.state.todo[val]}
/>
<label>{val}</label>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>

ReactJS clearing an input from parent component

I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<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>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<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>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}

How to get values/properties from a react-bootstrap checkbox?

I'm trying to use the react-bootstrap checkbox (https://react-bootstrap.github.io/components.html#forms-controls) and I need to fire an event when it changes state. It would also be great to be able to programatically un/check it and/or tell if it is checked. Unfortunately when the code is transpiled and rendered it wraps the input in a div.
How can I find this in the dom and manipulate it?
My code looks similar to this:
import React, { PropTypes } from 'react';
import { Checkbox } from 'react-bootstrap';
const EditItem = (props) => {
return (
<div>
<Checkbox style={{ marginLeft: '15px' }} >{props.itemLable}</Checkbox>
</div>
);
};
export default EditItem;
And the browser renders this:
...
<div class="checkbox" style="margin-left: 15px;">
<label>
<input type="checkbox">
</label>
</div>
...
I see the inputRef prop in the documentation but I can't find any examples of this or get it to work myself.
There are two ways: The React way and the not-so-React way.
The React way is to set the child component's state by passing it props and respond to changes in its state by attaching event handlers. In the case of Checkbox, that means setting the checked and onChange props.
Note in the below example how the parent component (App) keeps track of the Checkbox's state and can both set it with this.setState and query it with this.state.checkboxChecked.
const { Checkbox, Button } = ReactBootstrap;
class App extends React.Component {
constructor() {
super();
this.state = { checkboxChecked: false };
this.handleChange = this.handleChange.bind(this);
this.handleIsItChecked = this.handleIsItChecked.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<div>
<Checkbox
checked={this.state.checkboxChecked}
onChange={this.handleChange} />
<Button type="button" onClick={this.handleToggle}>Toggle</Button>
<Button type="button" onClick={this.handleIsItChecked}>Is it checked?</Button>
</div>
);
}
handleChange(evt) {
this.setState({ checkboxChecked: evt.target.checked });
}
handleIsItChecked() {
console.log(this.state.checkboxChecked ? 'Yes' : 'No');
}
handleToggle() {
this.setState({ checkboxChecked: !this.state.checkboxChecked });
}
}
ReactDOM.render(<App/>, document.querySelector('div'));
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.30.8/react-bootstrap.min.js"></script>
<div></div>
The not-so-React way is to get a reference to the rendered DOM element and access its checked property directly. I don't recommend this, because it necessarily pollutes your lovely functional React code with icky imperative code. Nevertheless, with React-Bootstrap you can do it by setting the inputRef prop, as in the below example:
const { Checkbox, Button } = ReactBootstrap;
class App extends React.Component {
constructor() {
super();
this.handleIsItChecked = this.handleIsItChecked.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<div>
<Checkbox
onChange={this.handleChange}
inputRef={ref => this.myCheckbox = ref} />
<Button type="button" onClick={this.handleToggle}>Toggle</Button>
<Button type="button" onClick={this.handleIsItChecked}>Is it checked?</Button>
</div>
);
}
handleIsItChecked() {
console.log(this.myCheckbox.checked ? 'Yes' : 'No');
}
handleToggle() {
this.myCheckbox.checked = !this.myCheckbox.checked;
}
}
ReactDOM.render(<App/>, document.querySelector('div'));
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.30.8/react-bootstrap.min.js"></script>
<div></div>
Thanks for the above answers. I generalized the above slightly for use when you have more than one checkbox in a given component:
constructor() {
super();
this.state = { YourInputName: false };
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
render() {
return (
<div>
<Checkbox
name="YourInputName"
onChange={this.handleCheckboxChange} />
</div>
);
}
handleCheckboxChange(event) {
const target = event.target
const checked = target.checked
const name = target.name
this.setState({
[name]: checked,
});
}
Have you tried setting an onChange property to your checkbox?
handleChange(event) {
this.setState(*set checkbox state here*);
}
<Checkbox onChange={this.handleChange}></Checkbox>

Categories