1

It is a wrapper around html5 input with a custom validation. Having encountered an uncountable number of limitations of input type number, I have decided to switch to type text. Unfortuntaly this app must run in mobile environment and customers require that when a user focuses on the input a digital keyboard shows up. I tried to implement dynamic type switching with onFocus and onBlur events which works just fine on iOS devices. This code however breaks on Android devices: it seems that type does not change and the regex is not applied to the input.

I am creating a react input component

class InputNumber extends Component {
    constructor(props) {
        super(props);
        this.re = props.regex ? props.regex : /^(?:[1-9]\d*(?:\.\d{0,2})?)?$/;
    }
    componentWillReceiveProps(nextProps) {
        // check new props and reassign the stake if needed
        if (this.input && this.currentVal !== nextProps.newValue) {
            if (nextProps.newValue === "0") {
                this.input.value = "";
            } else {
                this.input.value = nextProps.newValue;
            }
        }
    }

    makeTypeText = e => {
        e.target.type = "text";
    };

    makeTypeNumber = e => {
        e.target.type = "number";
    };

    setValue = e => {
        const re = this.re; // pattern that we will validate our input against
        const currentVal = e.target.value.split(""); // in order to validate we need to know what would be the next value
        const newChar = e.key; // we transform the current value into an array and
        let nextVal = [...currentVal]; // create a new array with previous values
        const start = e.target.selectionStart; // and then place the input char into the current cursor position
        const end = e.target.selectionEnd;
        if (e.key === "Backspace") {
            // if backspace is typed
            if (start === end) {
                nextVal.splice(start - 1, 1); // if there is no selection just delete the digit before the cursor
            } else {
                nextVal.splice(start, end - start); // if the user has selected several digits just replace them with empty string
            }
        } else if (e.key === "Delete") {
            // if delete is typed
            if (start === end) {
                nextVal.splice(start, 1); // delete a digit before after  the cursor if no selection
            } else {
                nextVal.splice(start, end - start); // delete selection
            }
        } else {
            nextVal.splice(start, end - start, e.key); // insert digit before the cursor or replace selection with a digit
        }
        const valueAfterInput = nextVal.join("");
        // check the value that would result from input against the regular expression
        if (!re.test(valueAfterInput)) {
            e.preventDefault(); // if the next value is invalid we prevent the user action
        } else {
            this.currentVal =
                valueAfterInput.slice(-1) === "."
                    ? valueAfterInput.substr(0, valueAfterInput.length - 1)
                    : valueAfterInput; // assign the current value to a class variable so we can check it against incoming props
            this.props.action(valueAfterInput); // dispatch an action
        }
    };
    render() {
        return (
            <div className="form-field__input">
                <input
                    type="number"
                    onKeyDown={this.setValue}
                    onFocus={this.makeTypeText}
                    onBlur={this.makeTypeNumber}
                    ref={input => (this.input = input)}
                    className={cn("form-input", "form-input_focus", {
                        "form-input_orange": this.props.hideCurrency
                    })}
                    placeholder={t("Stake...")}
                />
                <span
                    className={cn(
                        "form-field__action",
                        "form-field__action_showed",
                        "form-field__action_text",
                        {hidden: this.props.hideCurrency}
                    )}
                >
                    ₽
                </span>
            </div>
        );
    }
}

InputNumber.propTypes = {
    newValue: PropTypes.string,
    hideCurrency: PropTypes.bool.isRequired,
    action: PropTypes.func.isRequired,
    regex: PropTypes.object
};

export default InputNumber;

Any thoughts on how to remedy this situation?

P.S.: stop marking as duplicate before understanding the issue.

magom001
  • 608
  • 6
  • 16
  • Honestly, resolve the issues you had with the correct input type. While you may be able to work around your issues by bodging another input type to suit your needs, it just takes one change to that element (which I guarantee will happen in the future) and your application may become completely useless. Fix the real problems and use the correct input type. – Reinstate Monica Cellio Mar 26 '18 at 08:22
  • https://stackoverflow.com/questions/6178556/phone-numeric-keyboard-for-text-input – Dominic Mar 26 '18 at 08:23
  • 1
    Archer, it simply doesn't work well. For example decimal places in Germany are done with commas. Sometimes you want to allow numerics to be typed, but show the number as a formatted string etc – Dominic Mar 26 '18 at 08:25
  • type tel does not have '.' on iOS. Moreover it seems that chrome on android completely ignores the validation. Archer, pointless commentary. – magom001 Mar 26 '18 at 08:39
  • @Dominic Tobias, please remove the "duplicate" flag. I have not fount a working solution on the internet as of today. I need to input a float number with a period separator. Type 'tel' does not work because it has not '.' button. This code DOES work on iOS. – magom001 Mar 26 '18 at 08:47
  • Removed, I can't remember the exact solution (code is at home) but it was something similar to those answers which worked on both ios and android in my last project – Dominic Mar 26 '18 at 09:02

0 Answers0