SoFunction
Updated on 2025-04-07

A brief discussion on the controlled and uncontrolled components of React deep programming

There is not much information about controlled components and uncontrolled components on the official website and domestic Internet. Some people think it is possible or not, and they don’t care. This just shows the power of React and meets engineering needs of different sizes. For example, if you just do a simple data display like ListView and shoot the data, then the for-recycling and {} is enough, but there are a large number of reports in the background system, and different forms are linked, and it is really not possible to lack controlled components.

Controlled and uncontrolled components are the entrances for React to process forms. From the perspective of React's idea, the author must let the data control everything, or simply understand that the generation and update of pages must faithfully execute JSX instructions.

However, form elements have their own special features, and users can change the display of the interface through keyboard input and mouse selection. The interface changes also mean that some data have been changed. The more obvious ones are input value, textarea's innerHTML, radio/checkbox's checked, and the less obvious ones are option's selected and selectedIndex, which are passively modified.

 <input value="{}"/>

When it is taken by the component, when the user inputs and modifys it, then JSX re-sweeps the view again. At this time, should the user's new value or the state's new value be taken? Based on this divergence, React provides a compromise solution, which supports both, thus creating today's theme.

React believes that value/checked cannot exist alone and needs to be used with properties or events that control value/checked, such as onInput/onChange/disabed/readOnly. Together they form a controlled component, which is controlled by JSX. If the user does not write these extra properties and events, the framework will add some events to it, such as onClick, onInput, onChange, which prevents you from entering or selecting, making it impossible to modify its value. Inside the framework, there is a stubborn variable, which I call persistValue, which keeps the value JSX assigned to it last time, and can only allow internal events to modify it.

Therefore, we can assert that the controlled component is control over the value that can be accomplished through events.

In controlled components, persistValue can always be refreshed.

Let's look at the uncontrolled components again. Since value/checked has been occupied, React enables another group of ignored attributes in HTML that are defaultValue/defaultChecked. It is generally believed that they are in communication with value/checked, that is, if the value does not exist, the value of defaultValue is regarded as a value.

As we have already said above, the display of form elements is controlled by the internal persistValue, so defaultXXX will also synchronize the persistValue, and then the DOM is synchronized by the persistValue. But the starting point of uncontrolled components is to be faithful to the user's operations, if the user is in the code

 = "xxxx"

after

<input defaultvalue="{}"/>

It will not take effect again, it will always be xxxx.

How does it do this and how do you identify whether this modification comes from inside or outside the framework? I looked through the source code of React and it turned out that it had something called valueTracker to track user input

var tracker = {
  getValue: function () {
   return currentValue;
  },
  setValue: function (value) {
   currentValue = '' + value;
  },
  stopTracking: function () {
   detachTracker(node);
   delete node[valueField];
  }
 };
 return tracker;
}

This thing is then entered into the value/checked of the element, so we know the user's value assignment operation.

But value/checked is still two very core attributes, involving too many internal mechanisms (such as value and oninput, onchange, input method event oncompositionstart,

compositionchange, oncompositionend, onpaste, oncut), in order to slowly modify value/checked,

Also use it. If I want to be compatible with IE8, there is no such advanced gameplay. I'm taking another safer approach,

Only modify defaultValue/defaultChecked.

First I add a _uncontrolled attribute to the element to indicate that I have hijacked defaultXXX. Then add a switch to the set method that describes the object (the third parameter) and _observing. Update the view inside the framework, this value is false, after the update, it is set to true.

This way you know when = "xxx", is it modified by the user or the framework.

if (!dom._uncontrolled) {
  dom._uncontrolled = true;
  (dom, name); //Rewrite defaultXXX's setter/getter}
dom._observing = false;//At this time, the frame is modifying the view, so the switch needs to be turned offdom[name] = val;
dom._observing = true;//Open the switch to monitor the user's modification behavior

The implementation of inputMonitor is as follows

export var inputMonitor = {};
var rcheck = /checked|radio/;
var describe = {
  set: function(value) {
    var controllProp = () ? "checked" : "value";
    if ( === "textarea") {
       = value;
    }
    if (!this._observing) {
      if (!this._setValue) {
        //defaultXXX will only be synchronized once_persistValue        var parsedValue = (this[controllProp] = value);
        this._persistValue = (value) ? value : parsedValue;
        this._setValue = true;
      }
    } else {
      //If the user changes the defaultValue privately, then _setValue will be dropped      this._setValue = value == null ? false : true;
    }
    this._defaultValue = value;
  },
  get: function() {
    return this._defaultValue;
  },
  configurable: true
};
 
 = function(dom, name) {
  try {
    if ("_persistValue" in dom) {
      dom._setValue = true;
    }
    (dom, name, describe);
  } catch (e) {}
};

I accidentally posted such a brain-burning code, which is a bad habit of the coder. However, at this point, everyone understands that both official react and anu/qreact control user input.

So we can understand the behavior of the following code

  var a = (<textarea defaultValue="foo" />, container);
  (<textarea defaultValue="bar" />, container);
  (<textarea defaultValue="noise" />, container);
  expect().toBe("noise");
  expect().toBe("foo");
  expect().toBe("noise");
  expect().toBe("noise");

Since the user has not manually modified the defaultValue, dom._setValue has always been false/undefined, so _persistValue can always be modified.

Another example:

var renderTextarea = function(component, container) {
  if (!container) {
    container = ("div");
  }
  const node = (component, container);
   = (/^\n/, "");
  return node;
};
 
const container = ("div");
//Note this method. If the user manually changes the defaultValue in renderTextarea, _setValue becomes trueconst node = renderTextarea(&lt;textarea defaultValue="giraffe" /&gt;, container);
 
expect().toBe("giraffe");
 
// After _setValue, gorilla cannot be synchronized to _persistValue, so it is still girafferenderTextarea(&lt;textarea defaultValue="gorilla" /&gt;, container);
// expect().toEqual("giraffe");
 
 = "cat";
// What's going on with this? Therefore, non-monitoring attributes are processed in batches in diffProps, and in monitoring attributes, they are processed in a later method// !== _persistValue was detected, so _persistValue = is rewritten, so cat is outputrenderTextarea(&lt;textarea defaultValue="monkey" /&gt;, container);
expect().toEqual("cat");

Plain text class: text, textarea, JSX values, always converted to strings

The control of type="number", the value is always a number, if not filled in or is "", it will be converted to "0"

Radio has a linkage effect, and only one radio control with the same name under the same parent node can be selected.

The value/defaultValue of select supports arrays and does not perform conversions, but the user adds and deletes the option element below, and the selected will change accordingly.

In addition, select also has fuzzy matching and exact matching.

//Exact matchvar dom = (
  &lt;select value={222}&gt;
    &lt;option value={111}&gt;aaa&lt;/option&gt;
    &lt;option value={"222"}&gt;xxx&lt;/option&gt;
    &lt;option value={222}&gt;bbb&lt;/option&gt;
    &lt;option value={333}&gt;ccc&lt;/option&gt;
  &lt;/select&gt;,
  container
);
expect([2].selected).toBe(true);//Select the third one
//Fuzzy Matchvar dom = (
  &lt;select value={222}&gt;
    &lt;option value={111}&gt;aaa&lt;/option&gt;
    &lt;option value={"222"}&gt;xxx&lt;/option&gt;
    &lt;option value={333}&gt;ccc&lt;/option&gt;
  &lt;/select&gt;,
  container
);
expect([2].selected).toBe(true);//Select the second one

In all these things, React/anu has done a lot of work, and minis such as preact/react-lite may encounter pitfalls.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.