Controlled component input field onChange behaviour - javascript

I have a controlled component with an input field representing a day value. Since this day value can have 1-2 digits, e.g. one has to to delete 12 to enter 21.
Since the onChange react-event behaves like the input DOM-event, I can only delete or enter one digit and the event is fired, hence the whole model gets updated too early with day set to one after I deleted a digit.
<input
name={name}
type="number"
value={value}
onChange={(e) => { onChange(e.target.value) } }
/>
Thanks to defaultValue change does not re-render input I can handle this with an uncontrolled component input with an onBlur event. Using key ensures, that a new rendering happens, when the model is changed by other means:
<input
name={name}
type="number"
defaultValue={value}
key={value}
onBlur={(e) => { onChange(e.target.value); }}
/>
But honestly, this feels like a hack. How do you pros solve this scenario? Have I overlooked something simpler? Do you use a timeout-function before updating the model (thus waiting for complete user-input) aka debounce?

Why can't you use both onChange and onBlur ?
class NumberChooser extends React.Component{
constructor(props){
super(props);
this.state = {
fieldValue: props.value,
time: ''
}
}
onChange(e){
this.setState({fieldValue: e.target.value});
}
render(){
return (
<input
name={this.props.name}
type="number"
value={this.state.fieldValue}
//key={value} not sure what do with this
onChange={this.onChange}
onBlur={(e) => this.props.onChange(e.target.value)}
/>
);
}
}
export default NumberChooser;

Thanks to Andrew's input it came to me, that using local state could be a solution for my problem. Now the component is a class and not a functional component anymore. Still it feels a bit awkward to store the displayed value of a field locally just to be able to use onChange without midst-editing field updates coming from the main state. But it seems to be the way, if one wants to use controlled components with a single source of truth and I'll just consider the local state as UI-state ;-)
export default class NumberChooser extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = { value: this.props.value };
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value });
}
}
render() {
return (
<div className="col" name={`NumberChooser_${this.props.name}`} style={ isDebug ? debug.borderStyle : {} } >
<IncButton
onButtonClicked={() => this.props.onChange(this.state.value+1)}
/>
<input
name={this.props.name}
type="number"
value={this.state.value}
onChange={(e) => { this.setState({ value: e.target.value }); } }
onBlur={(e) => { this.props.onChange(e.target.value); }}
/>
<DecButton
onButtonClicked={() => this.props.onChange(this.state.value-1)}
/>
</div>
);
}
}

Related

How can I make the default value of an input changeable in ReactJS?

I am new to working with React so this might be an easy one that I am struggling to figure out. Let's say I have this very simple class, that renders an input with a default value. I want this input to have the value I set it, but also being able to change it. However, when it renders, the input field is filled with "hi" and I can't write or delete anything over it. How can I make this possible?
export class Hello extends React.Component {
render(){
let i = "hi"
return(
<div>
<input type="input" value={i} />
</div>
);
}
}
Store the input value as part of your component's state, and listen for the onchange event on the input field to update the component's state.
export class Hello extends React.Component {
state = {
textbox: "hi",
};
render() {
return(
<div>
<input
type="input"
value={this.state.textbox}
onChange={ev => this.setState({ textbox: ev.target.value })}
/>
</div>
);
}
}
See examples: https://reactjs.org/docs/forms.html#controlled-components
You need to use state for this purposes. Look here: https://reactjs.org/docs/forms.html#controlled-components
I'am adding a bit different answer, its depends if your component is Controlled or Uncontrolled component, see difference in docs.
Controlled example you can find in other answers or docs.
Uncontrolled example is usually when using <form/> elements, in this case you can just add defaulValue prop as mentioned in related docs.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" defaultValue="hi" ref={this.input} />
<input type="submit" value="Submit" />
</form>
);
}
}

React onChange doesn't fire on text input when formatter.js is used

I'm using a formatter.js to format some input box. I want to connect this formatter to my react app so I've write a simple module but onChange function doesn't fire. When I remove the formatter library the input box works as planned. But I want to format the inputs and also use the values inside my React application.
After a brief search over the internet I've came across with this question;React: trigger onChange if input value is changing by state? but I'm not sure how can I apply that solution to my application.
ReactMaskedInput.js
import React, { Component } from 'react'
class ReactMaskedInput extends Component {
constructor(props) {
super(props)
this.onChangeHandler = this.onChangeHandler.bind(this)
this.state = {
value: ""
}
}
onChangeHandler(event) {
this.setState({
value: event.target.value
})
alert(this.state.value)
}
componentDidMount() {
let dataMask = this.props.dataMask
window.$(`#${this.props.id}`).formatter({
pattern: dataMask
});
}
render() {
return (
<div >
<h3>
<b>{this.props.header}</b>
</h3>
<input
id={this.props.id}
type="text"
placeholder={this.props.placeHolder}
className="form-control"
onChange={event => this.onChangeHandler(event)}
name={this.props.name}
>
</input>
</div>
)
}
}
export default ReactMaskedInput
Index.js
ReactDOM.render(<ReactMaskedInput
id="myMaskedInput"
header="Masked Input Phone"
onChange={(event) => { deneme(event); }}
dataMask={"({{999}}) {{999}}-{{9999}}"}
name="maskedInput1"
placeHolder="Please enter a valid phone number"
validationInitiatorNr={10}
// onChange={(phoneNr) => validatePhone(phoneNr)}
/>, document.getElementById('myFormattedInput'))
Fix your onChangeHandler code
You have to call the 'onChange' handler you passed as an attribute in code of your ReactMaskedInput class explicitly. I guess you are assuming that it would get called automatically. Note that ReactMaskedInput is a component you created, and not an HTML tag 'onChange' of which gets called by React.
onChangeHandler(event) {
this.setState(() => ({
value: event.target.value
}), () => {
this.props.onChange(event) // Call 'onChange' here
alert(this.state.value) // alert should be inside setState callback
})
}

How to change the value of a TextInput using reference in React Native?

I am using React Native 0.57.8 and React 16.7.0. I am creating an on-screen keyboard for Android TV which will be used as a library. I have a TextInput to whom I have assigned a reference. How can I use this reference to change the value of the TextInput?
constructor(props) {
super(props);
this.emailRef = React.createRef();
}
<TextInput
ref={this.emailRef}
placeHolder="Email Address"
blurOnSubmit={false}
/>
<Keyboard textInput={this.emailRef} />
Inside the library:
<Button
text="change value"
onPress={() => {
this.props.emailRef.current.props.value =
this.props.emailRef.current.props.value + "q";
}}
/>
Use state they all say without knowing why I exactly need to update UI without using state. In my case the text input was using state, and when typed really fast, the asynchronous state and UI update lags behind the speed of typing, and cursor lags couple of letters behind causing in errors in typing. Only way to prevent this is not to use any state! if you do not use state, you cannot give text input an initial value without making it readonly. To give TextInput an initial value, we can use ref and set native props programmatically upon component mount event. like so:
const setInitialBio = React.useCallback(() => {
if(inputRef.current) {
inputRef.current.setNativeProps({ text: "initial value goes here" })
}
}, []);
I think what you need is a controlled input.
Here is how I would do that:
constructor(props) {
super(props);
this.emailRef = React.createRef(); // might not need this
this.state = {
value: ''
}
}
<TextInput
value={this.state.value}
onChangeText={(text) => this.setState({text})}
placeHolder="Email Address"
blurOnSubmit={false}
/>
<Button
text="change value"
onPress={() => {
// just use `this.state.value` to do whatever you need with it
}}
/>
Set text in state method then update state in pressed button
then set in text like this
<Text>
{this.state.myText}
</Text>
You can't change a prop directly within the component - Props can only be derived from a parent component, but cannot be modified, so you can't do:
this.props.emailRef.current.props.value = this.props.emailRef.current.props.value + "q";
Also, you reference this.props.emailRef in your library, whereas the keyboard doesn't have the emailRef prop - it has the textInput prop.
Try this:
constructor(props) {
super(props);
this.emailRef = React.createRef();
this.state = {
value: "Initial Input"
};
this.handleInput = this.handleInput.bind(this);
}
handleInput(input) {
this.setState({ value: input });
}
render() {
return (
...
<Keyboard textInput={this.emailRef} onInput={this.handleInput} />
<TextInput ref={this.emailRef} value={this.state.value} />
...
);
}
Inside the library:
<Button
text="change value"
onClick={() => {
this.props.onInput(this.props.textInput.current.props.value + "q");
}}
/>

DRY react input handling causes cursor to jump

I was trying to DRY up my react forms a bit, so I wanted to move my basic input-handling function to a utility module and try to reuse it. The idea was to update an object that represented the state, return it in a Promise, and then update the state locally in a quick one-liner.
Component
import handleInputChange from "./utility"
class MyInputComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: {
title: ""
}
};
}
render() {
return (
<input
type="text"
id="title"
value={this.state.data.title}
onChange={e => handleInputChange(e, this.state).then(res => {
this.setState(res);
})}
/>
)
}
};
utility.js
export const handleInputChange = (event, state) => {
const { id, value } = event.target;
return Promise.resolve({
data: {
...state.data,
[id]: value
}
});
};
It seems to work fine, however the issue is that the input's cursor always jumps to the end of the input.
If I use a normal local input handler and disregard being DRY, then it works fine.
For example, this works without issue regarding the cursor:
Component
class MyInputComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: {
title: ""
}
};
}
handleInputChange = event => {
const { id, value } = event.target;
this.setState({
data: {
...this.state.data,
[id]: value
}
});
};
render() {
return (
<input
type="text"
id="title"
value={this.state.data.title}
onChange={this.handleInputChange}
/>
)
}
};
Any idea why the cursor issue would happen when I tried to be DRY? Is the promise delaying the render, hence the cursor doesn't know where to go? Thanks for any help.
I'm a little confused with what you are trying to do in the long run. If you want to be DRY maybe your react component could look like this
render() {
return (
<input
type={this.props.type}
id={this.props.title}
value={this.props.value}
onChange={this.props.handleInputChange}
/>
)
}
this way you pass everything in and it stay stateless and everything is handle at a high component
however doing the way you have asked could you not use something like the below and you would not need to use a promise to return an object?
onChange={e => this.setState(handleInputChange(e, this.state))}

React JS range slider - using an array for the value?

I'm wondering how you'd go about getting the value of an index in an array using an input[type="range"] in React, similar to this example?
What I'm trying to do is this: pass in a range of values and be able to print out those values by using the index of the array.
As you'll see from the example code below, I am initially rendering the value I want (in this case, 'Apples') but then when I use the slide it then starts rendering the index of the array, instead of the values.
Here's what I've got so far:
class RangeSlider extends React.Component {
// constructor
constructor(props) {
super(props);
this.state = {
value: props.value[0]
};
}
handleChange(event, index) {
const { value } = this.state;
this.setState({ value: event.target.value});
}
render() {
const { value } = this.state;
const { label } = this.props;
return (
<div className="mb4">
<label className="f4 mt0">
{label} <b className="fw7 pl1">{value}</b>
</label>
<input
className="w-100 appearance-none bg-transparent range-slider-thumb-custom"
type="range"
min={0}
max={this.props.value.length - 1}
step={1}
value={value}
onChange={this.handleChange.bind(this)}
/>
</div>
);
}
}
window.onload = () => {
ReactDOM.render(
<RangeSlider
label={"I would like some "}
value={["Apples", "Oranges", "Pears"]} />,
document.getElementById("main"));
};
Link to a Codepen.
The only problem you were having is that on initial load, your state object was set to access the value in the array correctly. However, everytime the handleChange method fires, it overwrites the state with just an integer, and thus does not do what you are expecting.
If you instead just set the "value" property in your state object to a default value of "0", you can just track the index, and change one more line in your code, and it should work just fine.
First change your state to look like this:
this.state = {
value: 0
};
Next, change to this inside your jsx body:
{label} <b className="fw7 pl1">{this.props.value[value]}</b>
This way, you are always going to print out a value, and not an integer to the screen. I think this results in you having to add far less code.
Working Codepen.
Here is the updated code
import React from 'react'
class Range extends React.Component {
// constructor
constructor(props) {
super(props)
this.state = {
value: 0
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({ value: this.props.value[event.target.value]})
}
render() {
const { value } = this.state
const { label } = this.props
return (
<div className="mb4">
<label className="f4 mt0">
{label} <b className="fw7 pl1">{value}</b>
</label>
<input
className="w-100 appearance-none bg-transparent range-slider-thumb-custom"
type="range"
min={0}
max={this.props.value.length - 1}
step={1}
onChange={this.handleChange}
/>
</div>
)
}
}
export default Range

Categories

Resources