Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a controlled form in react that allows upper case letters only?

Sorry for the long explanation, the question is at the end.

There is an example given on the reactjs site ( https://reactjs.org/docs/forms.html ) that looks like this:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});
}

These tutorials usually say that in controlled forms (say in an input field) when you press a key, then onChange is called, that calls the handleChange event, that calls setState, and that re-renders the component, that will show the changed value. But this is not the whole truth. By the time the native DOM input field calls onChange, the input field was already "rendered" by the browser, and React does not re-render it. Actually it compares the state of the (re-rendered) virtual DOM element with the state of the native DOM element, and then it finds out that there is no difference, so it does nothing. In this case, the action has no side effect (other than changing the component's state).

I know that for a beginner, in the beginning of the tutorial, it may be confusing to tell the whole story at once. But this "toUpperCase()" example in the tutorial is misleading, because it suggests that by changing the value to value.toUpperCase() in handleChange will make the component only accept captialized letters, without side effects. But this is not true, and anyone can try this: just select a part of the text in the middle of the input field, and press a key. It will surely be converted to upper case, but in this case React will re-set the value attribute of the input, causing the text cursor (insertion point) to move to the end of the text. Which is a side effect, that can be very distrubing. For example, if the user wanted to replace a word in the middle of the text, then he just can't do it in any practical way. He will say that this is wrong, it is not working - and he will be damn right. In this particular case, I know that I can set text-transform:uppercase and store the lower case version in the store, but that is faulty too: if I need it to be upper case, then of course it should be in upper case in the store. The behaviour of the view must not determine the data representation in the store. Views should only be views, and nothing else.

I have checked many React related tutorials, and all of them have this "flaw". For example, here is a diagram from reactjs.org that shows Flux in action:

enter image description here

It suggests that the View is rendered after the action went through the dispatcher, the stores, then the controller-view. But the reality is that the View not only emits an action. It will also change its appearance before the action is emitted. React wants to see DOM elements as views that emit actions, but in reality they do other things.

The very same mistake appears in the reactjs tutorial, with the select element. ( https://reactjs.org/docs/forms.html ) They just say that by changing the "multiple={true}" attribute, you can have a multi select. No you can't, it just does not work. When you select multiple items in a multi select, the browser will call the onChange event multiple times: a separate call for every item selected. As a result, it will be impossible to set the state of the component to the set of selected items (at least not from the event handler). I COULD setup a timer, collect all changes emitted from onChange, and the finally call setState() with the collected items. But that is against React's rules, because there should be a single source of truth. And in this case, there will be multiple sources (at least for a while, until all events are collected). And anyone can see that in a complex application with many async handlers, this will eventually lead to bugs which are hard to debug.

A very simple thing ("only accept upper case letters in an input box") appears to me a hard thing to do correctly. Either I have to implement my own input box in JavaScript, or write a custom rendering method for input fields that keeps the cursor position at the correct place when value changes. Both seem to be bad ideas.

After Googling around, I have noticed that there are tons of components re-implementing native elements. There are literally hundreds of select and multiselect components written for React, and I suspect that this is partly because of how native DOM elements behave. (E.g. they are not pure views.)

Question: does anyone use react with native DOM components? And if so, how do you achieve the true "controlled" behaviour without the side effects? Is it possible? Or should I not care, and choose some kind of UI toolkit that was written specifically for React? (But even with those, implementing an "upper case input field" may require extra programming.)

like image 914
nagylzs Avatar asked Dec 08 '17 06:12

nagylzs


People also ask

How do you make text uppercase in React?

To force an input field to uppercase in React:Use the toUpperCase() method to uppercase the field's value, e.g. event. target. value. toUpperCase() .

What are the two ways to control forms in React?

In React, there are two ways to handle form data in our components. The first way is by using the state within the component to handle the form data. This is called a controlled component. The second way is to let the DOM handle the form data by itself in the component.

What is handleSubmit in React?

handleSubmit gets the current value of state. value and adds it to the array of webhooks . The rest of App is responsible for rendering the other React components that make up the page and passing data to the components that require it.


2 Answers

I have written simple toInputUppercase function which override e.target.value and applied attribute onInput to <input /> element which I want to be capitalize.

import React, { useState } from "react";
import ReactDOM from "react-dom";

const toInputUppercase = e => {
  e.target.value = ("" + e.target.value).toUpperCase();
};

const App = () => {
  const [name, setName] = useState("");

  return (
    <input
      value={name}
      onChange={e => setName(e.target.value)}
      onInput={toInputUppercase} // apply on input which do you want to be capitalize
    />
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

Hope it will work for you.

like image 57
Sagar Avatar answered Oct 14 '22 02:10

Sagar


This should be sufficient:

handleChange(event) {
  const input = event.target;
  const start = input.selectionStart;
  const end = input.selectionEnd;

  this.setState(
    {value: input.value.toUpperCase()},
    () => input.setSelectionRange(start, end)
  );
}

Also check: http://blog.vishalon.net/javascript-getting-and-setting-caret-position-in-textarea

like image 22
Mat_90 Avatar answered Oct 14 '22 01:10

Mat_90