how are read-only fields implemented in React? - javascript

I put together this SSCCE to test read-only fields in forms created using ReactJS versus plain HTML forms and fields (also in jsfiddle):
<!doctype html>
<html>
<head>
<title>comparing ReactJS and simple HTML forms</title>
<script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react.js"></script>
<script src="https://cdn.jsdelivr.net/react/0.14.0-rc1/react-dom.js"></script>
</head>
<body>
<div>
<h3>HTML form</h3>
<form>
<input type='text' value='your name'/>
</form>
</div>
<div>
<h3>form created with ReactJS</h3>
<div id='reactForm'>
</div>
</div>
<script type='text/javascript'>
var rce = React.createElement.bind(React);
var SillyReactForm = React.createClass({
render: function() {
return rce('form', {}
, rce('input', { type: 'text', value:'your name'}));
}});
var form = rce(SillyReactForm, {});
ReactDOM.render(form, document.getElementById('reactForm'));
</script>
</body>
</html>
Sure enough, the field created with ReactJS is read-only and in fact I see this message in the console as well:
Warning: Failed form propType: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
My question is how is this enforced by ReactJS as, examining the DOM elements I see no essential difference between the plain HTML form and the ReactJS-generated one:
Is ReactJS executing some behind-the-scenes JavaScript that sets that field to effectively read-only? And if so, how can I discover this kind of logic that's attached to my DOM elements? Doesn't this run counter to the idea that ReactJS is a library and not a framework (and as such more transparent and easier to reason about?)

That warning isn't telling you that React is forcing the input to be readonly. It's telling you that React isn't. You'll end up with a mutable input representing immutable state. The user will be able to make changes to the input, but as soon as a re-render is triggered, those changes will be lost as the input is regenerated from the application state.
That's why React is telling you to add either the readOnly or onChange attribute. Either make the input immutable to match up with the state, or make the state mutable to match up with the input.
I haven't used defaultValue before, but I imagine it works by automatically hooking up an onChange event to some state behind the scenes. I think React probably also does the same if the value attribute is omitted, but does not do this if the value field is present, so as not to conflict with any state binding in the component, and instead throws this warning.

Related

Clearing a text field value using a final-form calculator

I am trying to use the final-form calculator to clear a field whenever another field has changed.
In my example, I have two fields. Whenever the first field changes, the second field is cleared. This is expected.
However, A problem arises when the parent component of the form is re-rendered. Each time the parent component calls to it's render function, the second field is cleared even though the first field has not changed. This can be observed by clicking the forceUpdate button at the top.
Is it possible to prevent the second field from clearing like this? Preferably without using shouldComponentUpdate
I have been able to resolve it by moving the decorators array outside of the component.
const decorators = [calculator]; // declared outside of App
And reference the value in the form props
<Form
decorators={decorators}
...

Provide both :value and v-model on a radio type field

<input type="radio" :value="myValue" v-model="value" />
I'm trying to create a radio button and wrap it in a component, so value becomes a variable. The problem is that I'm getting the following error:
:value="myValue" conflicts with v-model on the same element because the latter already expands to a value binding internally
I've tried to replace v-model with direct bindings but I cannot replicate the same functionality. Why does this error appear in this case? This is taken directly from official docs for the radio buttons.
v-model is just shorthand for :value="someVar" #input="someVar = $event". meaning you are assigning the value twice. Depending on your need you can do :value="someVar" and then have a custom function handle the input like so:
#input="someFunc" this function would accept the input (by default) and then you could update myVar as you desire.
See here for a more detailed explanation

quirks of input element in React

I'm new to React, sorry if my questions are trivial.
In React, if I write:
<input value="" />
then it will be an read-only field, and React forces me to add an onChange handler as:
<input value={this.state.greeting} onChange={this.handleChange} />
where handleChange() update component's state.greeting, therefore value gets updated too
Below are my questions:
Q1-why <input value="" /> is not read-only in plain html but read-only in React? How does React make it read-only
Q2-I found that if I write the code as below it also works, the input is not read-only
<input onChange={this.handleChange} />
and isn't that this approach better and more concise? because the internal value will get updated automatically in the browser, therefore we don't need to include an value attribute to read the data back from the state, which is unnecessary in most of times, so why I always see code like:
<input value={this.state.greeting} onChange={this.handleChange} />
Additional info:
some says it is controlled form elements that needs to have value attribute, but why we need to have a value attribute to read from the state? and when we type sth into the input, the onChange handler already updates the state, so it is still controlled.
To know how React makes your tags readonly, you will need to study the source-code that runs at your end and/or view the generated HTML. If still unsure about it, then you might want to send your first question to the authors of the tool.
The state is not on the server, unless you are polling or doing something of the like. It's in your browser as well. The value property specifies the initial value of your HTML element, that is, before you do anything your tag will have a value. In your case, your tag is controlled by React, but you need to initialize it. Benefits:
you will have the initial value
you will have a more readable code
your code will be written in the React-way, so you will not need to worry of unpleasant surprises

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:
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.)
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
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.
You can simply add this property to the input field :
onInput={(e) => e.target.value = ("" + e.target.value).toUpperCase()}
Hope it works :)
You are right in what you observe but the issue does not come from React, it comes from the browser itself.
Consider the following example:
function handler() {
const elt = document.getElementById('test');
elt.value = elt.value.toUpperCase();
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<input id="test" type="text" value="default value" onKeyPress="handler();" />
</body>
</html>
How would you make it work even before talking about React ? React can't override the browser behavior.
If you want to uppercase everything while keeping the cursor position, you could look at rich text editing with draft js for example.
easy: css + normalization (toUpperCase()) after submit
text-transform: uppercase;

React: Will a state change cause an entire component to be redrawn?

I'm creating a form in react that has conditional flow. For example two input fields might be hidden when some select option is chosen. Is it a good idea to create states based on the select options and make the visibility of the input elements to depend on the current state. Or shall I add refs to the input elements and make the select cause a change event which will be used to manipulate the visibility of the inputs?
Unless you have any complicated logic for deciding whether to draw an input or not I think it's perfectly fine, even encouraged, to simply tie it to some state variable. Here's an example using Reacts two-way binding addon:
var MyForm = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {hasName: false};
},
render: function() {
var nameInput = null;
if (this.state.hasName) {
nameInput = <label>
Name:
<input type="text" />
</label>;
}
return <form>
<label>
<input type="checkbox" checkedLink={this.linkState('hasName')} />
Do you have a name?
</label>
<br/>
{nameInput}
</form>;
}
});
React.render(<MyForm/>, document.body);
http://jsfiddle.net/p4u1qhym/
And when you're using event handlers you should still set a state variable for the input: Even though React is smart enough to keep any DOM state intact during re-rendering (e.g. a visibility styling option), it won't always work. Imagine for example a situation where you serialize the forms state and want to use it to initialize another form later on. React won't be able to infer that the name input should not be displayed, and renders it.
There still exist situations when it's useful to use this.refs however, for example when setting the focus to a particular input, as described here.
From the title of your question I assume you are worrying about performance: Yes, the entire component will be re-rendered, but only in virtual DOM. React then finds all the differences to the actual DOM and only applies the changes required for the two to match. You can read about it here. As this whole process is very efficient, there should be virtually no difference in performance to setting some style attribute on the actual DOM node using this.refs. And it's a lot less verbose!

Categories