React.js controlled text cursor focus issue - javascript

I have a simple controlled input of type number like below.
<input type="number" value={+value} step={1} onChange={this.updateMyChange} />
My value often returns a decimal number like 123.123 . My problem is, when I try edit the value. The cursor loses focus and shifts to the beginning ignoring the whole numbers as soon as the decimal places are cleared. Like below:
How do I address this? Immediately after the decimal places are cleared, the cursor jumps to the beginning thereby making it impossible to edit the whole numbers. Any help would be appreciated.
Update
Below is the remaining code as requested by the user below.
render() {
const {value} = this.state;
return (
<input type="number" value={+value} step={1} onChange={this.updateMyChange} />
)
}
And my updateMyChange method is simply
updateMyChange(e) {
this.setState({ value: e.target.value });
}
It does nothing much simply sets the new value. The cursor position jumps to the end as soon as decimal places are cleared. It does not set cursor for whole numbers.

This is how React updates an input field's value:
node.setAttribute(attributeName, '' + value);
When you set value attribute using that method, the caret goes to the beginning of the field, regardless of using React. You can see what I am saying in this fiddle - https://jsfiddle.net/5v896g3q/
(Just try and position the cursor in the field, between changes).
According to MDN, setAttribute is unstable when dealing with value. The recommended way of changing value is by accessing the value property of the element, like element.value = newValue. If you use that approach, all seems to go as expected.
This is all I can tell for sure. Now let's speculate a little. When you type anything in that field, you are:
updating the value of the input
reading that value, and sending to React as state
React updates the value of the input, with the new state
When you are typing on the field, step 3 is likely to have no impact, because when the value comes back, the input already got it right. Except on the case with the float number. When your field reads 1., the actual value React updates the field with is 1. And React uses the evil method (setAttribute).
So, a workaround I found was setting the value of the field, using the proper method, before React touches it, on componentWillUpdate:
componentWillUpdate(nProps, nState){
this.refs.input.value = '0' + nState.value
}
The problem there, is that it is "numerizing" the value on every change, meaning I won't be able to have a point (1.). For that reason, I will only edit the input in case new value is 2 characters shorter than the old one (point + digit after point):
componentWillUpdate(nProps, nState){
if(this.state.value.length - nState.value.length === 2){
this.refs.input.value = '0' + nState.value
}
}
Working example - https://jsfiddle.net/bsoku268/3/
note: the fiddle is for demonstration purposes, and not supposed to be a bulletproof solution, as there are many ways of interacting with an input field, such as copy & paste, drag & drop, autofill, etc

Related

Convert user input with function and ng-model in AngularJS

Im trying to implement in an app, a unit converter from mm to inches and viceversa. This is part of a larger design app and the idea is that the user can work in mm-inch and use a button to change the displayed values and also be able to edit the input (like xPosition or shapeWidth).
I made this simple fiddle to show my problem.
As you can see, the converter works fine, but the input stops working and i can't edit the input, so the value is fixed with the initial value of the directive. Since this values can be modified, i can't use ng-init since its for initialization only.
If I use ng-model="this.value" the input starts accepting changes but the conversion stops working. The value should be inside the input, I can't have another number displayed at the side with the converted number.
Is there a way to enable the user input and also use the getValue or something similar to make the conversion when the buttons are pressed? Don't usually work in AngularJS but got this task so theres probably a directive option.
Thanks!
You should convert the value only on change of units. I changed a little bit your example here: https://jsfiddle.net/avgustin/y1khr2j8/5/
I used this.value in ng-model:
ng-model="this.value"
and changed the functions setUnit and convertUnit to do the conversion in both directions.
$scope.convertUnit = function(value) {
let appUnit = $scope.unit;
if(appUnit == 'in') {
return parseFloat((value / 25.4).toPrecision(3));
}
return parseFloat((value * 25.4).toPrecision(3));
}

Redux Field validation fails after supplying a value

A little context:
I have a form in which the user needs to select a date from a date picker, then submit the form with the selected date and some other pre-populated information. I am using Redux Field to wrap a custom component which includes the React Date Picker.
The problem:
I am using the validate prop for Redux <Field>, which simply enforces that the input value not be undefined. The problem is when I select a date I see the error message that I would normally see when validation fails. In other words the app thinks the date picker input value is undefined even though it clearly has a value.
Troubleshooting
I console logged this.props and checked the React Components Tab in dev tools, and observed the prop in question, "moveInDate", before and after selecting a date. I see an empty string prior to selecting a date (this is expected since I am setting an empty string as an initial value), and I see the expected value after selecting a date.
I also console logged the input value in the wrapped component, and I can see the name of the element and selected value are making down into it.
At this point I'm not sure where to look. Everything is updating as it should from what I can tell, yet the app still sees the input as having a value of undefined some how. I feel I am missing something as it relates to handling the input with Redux. It is useful to note I'm using this Field implementation on another page in the app, and it works without issue. I speculate there must be something elsewhere in the parent component messing with the state, but I'm not sure what to look for.
On a related note, I have a similar issue when selecting a dropdown that is also wrapped in Redux <Field>: the dropdown options don't always display, and I get the same validation bug as described above when I am able to select an option. I don't mean to get off topic with this, but I can't help but feel these issues are related.
I will post code I think will be helpful, but it is quite a lot in its entirety.
Field:
<Field
highlightDates={busyDates}
name="moveInDate"
type="text"
component={DateField}
label="Installation Date"
onChange={moveInDate =>
this.handleMoveInDateChange(
moveInDate,
leaseLength
)
}
validate={[required, this.dateAvailable]}
filterDate={(date) => {
const day = date.day();
return day !== 0 && day !== 6;
}}
minDate={
mode === MODE_RENEW &&
formValues?.currentMoveOutDate !== null
? moment(formValues?.currentMoveOutDate)
: moment().add(settings.blackoutPeriod, 'days')
}
maxDate={
mode === MODE_RENEW &&
formValues?.currentMoveOutDate !== null &&
moment(formValues?.currentMoveOutDate).add(1, 'week')
}
openToDate={
formValues?.moveInDate ||
this.getFirstAvailableDateForMoveIn()
}
Validate Rule
export const required = value => value ? undefined : 'You can\'t leave this empty';
Handler:
handleMoveInDateChange(moveInDate, leaseLength) {
const newMoveOutDate = getMoveOutDateByMoveInDate(
moveInDate,
leaseLength,
this.getBusyMoveOutDates()
);
if (leaseLength && moveInDate) {
this.props.formValues.moveOutDate = newMoveOutDate;
}
}
The handler simply calculates a move out date based on the move in date, then adds it to form values. There isn't a move out date picker in the form. I thought this handler could be causing a problem, but it's not doing anything with the move in date (the problematic input).
Final thought:
My question is admittedly similar to this one, but in my case the issue arises when inputting a new value, and not from dealing with a preloaded value.
Update
I noticed when selecting a date in the datepicker, the value never changes. I refactored the validate rule with braces so I could console log the arg, and there is nothing making it into the function. Conversely when I try this on another page using the same <Field> with datepicker I see the new value each time I change it.
I resolved the issue with keepDirtyOnReinitialize: true in the redux connect code.
I discovered the date picker was working correctly when I commented out the values I was assigning in initialValues in mapStateToProps, so it looks like the problem was the date picker always thought it had a pristine value, which is why the redux field never thought the value changed (from null to the selected value), so the validation logic didn't think anything was in there!
Hope this helps someone who stumbled across a similar problem.

How to bypass HTMLInputElement value parsing when setting number input value through JavaScript?

I am developing a touch keyboard for a project and running into this issue which I have not been able to resolve in an acceptable way.
The main issue is when I type a decimal on the touch keyboard on a number field, the input clears because when I set the text through JavaScript: htmlinputelement.value = "161."
I get the the following error: The specified value "161." cannot be parsed, or is out of range.
I've seen a few similar questions (The specified value cannot be parsed, or is out of range when using number pipe , The specified value "." cannot be parsed, or is out of range, and a few others)
The problem is resolved if I changed the type of the input from 'number' to 'text'. But doing this then opens up other issues. Some of the input fields use the step property and we have other input validation which can't be used if the type is not 'number'.
So my questions are
How can I type (using a usb keyboard) a decimal in an input field of type number without this issue? (How does the HTML input field handle value changes). I ask this since I am able to type 1. without issue but when I set it programmatically, it parses the value and fails.
Is it possible to bypass the HTMLInputElement value parse when setting it?
Edit:
My newest solution which seems to create the least amount of edge cases/other issues is to temporarily change the input type to 'text' to allow the decimal display, then change the type back to number after the next input. Let me know you guys thoughts on this!
You can use parseFloat() (or even parseFloat().toString() if you really want).
It's a bit longer than a +"0", but you don't have to check for the presence of ..
function doTest(event){
document.getElementById("input").value=parseFloat(event.target.innerText);
}
<input type="number" id="input"><br>
<button onclick="doTest(event)">1</button>
<button onclick="doTest(event)">2.</button>
<button onclick="doTest(event)">.3</button>
<button onclick="doTest(event)">4.5</button>

React can't null datetime-local input field

I'm using react together with react-semantic-ui and I'm having following input type:
<Input
onChange={e => this.onTextInputChange(e, 'date')}
value={this.state.searchQuery.date}
type='datetime-local'/>
onTextInputChange function sets input value as the state field and value of this input is bound to this field. Also, I have an additional button in my app which is responsible for clear form fields (by setting null on each state field).
state = {
searchQuery: {
date: null
}
}
Clear works pretty well for other fields, but the problem occurs when I touch datetime-local input. When I fill some part of the date I'm getting this message when I hit the clear button.
I'm able to clear this field only when I provide a full date. I know I can solve this issue by setting the value of this field to blank string ' ' during clear, but it isn't the best solution, because later I have to filter out those fields. I was even playing with react ref, but unfortunately I didn't achieve any positive result, what's more, blue cross which appears on hover clears the form. Any tips how can I handle it in a pretty way?
Edit #1:
event.preventDefault helped me to get rid of this validation message, but anyway input stays without cleared value.
You need to set the value of this.state.searchQuery.date to an empty string instead of null. In an input of type date, value={null} is not a valid as mentioned here. It is the only solution to this problem. If you specifically need null values you need to parse the empty string as null to avoid having to filter.
Value: A DOMString representing a date in YYYY-MM-DD format, or empty
Regarding the blue clear button, webkit has a pseudo-element input[type="date"]::-webkit-clear-button that allows you you style it after your needs. It is however pretty straightforward to implement a clear-button yourself as styling the button through CSS is not supported by all browsers.
Somehow I found the way to handle it.
I've made Ref in the constructor:
...
dateRef: any;
constructor(props: Readonly<Properties>) {
super(props);
this.dateRef = React.createRef();
}
...
assigned ref to my input:
<Input
...
type='datetime-local'/
ref={this.dateRef}
...
/>
In clearForm method I set '' on UI and null value in state.
private clearForm = (event: React.FormEvent): void => {
event.preventDefault();
this.date.current.inputRef.current.value = '';
this.setState({searchQuery: EMPTY_SEARCH_QUERY});
}
Also I'm preventing from search in case when input is invalid by accesing this.dateRef.current.inputRef.current.validity.valid
Maybe this solution isn't the best but it works.

Correct way to have calculated values in React.js

Setup
So basically I have 3 dimension fields (length, width and height) that are backed by state. I want to keep the state in inches but allow the user to switch the units on the display at will. Currently I'm only supporting inches and feet so I have a "dimension_unit" state that stores the currently displayed units and display_dimensions_multiplier that stores 1.0 for inches and 1.0/12.0 for feet (both are in state). I have everything working exactly as I want except for one thing.
Problem
All values I've tried have worked except when you try to use a decimal. Basically, in the background it's running throw the multiplier to get saved in state on change and then multiplies back in the value for the field which basically erases the trailing "." as you type so say you were trying to type 4.5, what you would actually get in the field as you typed is 45. But if you type 45 and then move the cursor between the 4 and 5 add the . you'll end up with 4.5...
My idea
The way I was thinking about solving it was basically have a state variable for the display_length and then have the regular length state as well then have the display_length stay the same unless they change units and have the length calculated and stored on change and then just persist that... and the same for height and width... but that seems very un-react-ive so what is the react-ive way of doing this? Or is that it?
Happy to post example code if needed, but there is a good bit of code for any component so... yeah... hopefully just the explanation is enough.
Edit1
Basic before fiddle: before
This is my idea I'm curious about: after
The main difference being:
_handleChange: function(ev){
ev.stopPropagation()
ev.preventDefault()
var key = ev.target.name;
var value = ev.target.value;
var indims = this.state.input_dimensions;
indims[key] = value;
this.setState({input_dimensions: indims});
value = (value / this.state.display_dimensions_multiplier);
var dims = this.state.dimensions;
dims[key] = value;
this.setState({dimensions: dims});
},
I think your solution is the right idea. A few similar ideas:
Only update your stored value when the input is committed (for example onBlur instead of onChange). You can apply generic realtime input validation to make sure it's a valid number.
Like you say use a separate "display" state (I would consider this an "input state") and when the component updates with the new stored value, only update the input state if it differs (when converted to the same units). This way you don't need to persist or worry about the "input state" outside the input components.
When the input has focus, don't update the input state when the stored value changes. When the input does not have focus or blurs, update the input state to the stored value.
I don't know that any of these solutions or the one you proposed is more "Reacty" than the others. I think it's really up to you.

Categories

Resources