Redefine the boundaries between controlled and uncontrolled components

The React official website defines the boundary between uncontrolled components and controlled components as underlined in the figure. Upon reflection, this definition lacks some completeness and rigor, such as the boundary between controlled and uncontrolled for non-form components (pop-ups, rotators). For example, do uncontrolled components really have the DOM itself to take over the presentation and change of data, as the text says?

In uncontrolled components, the business caller usually just needs to pass in an initial default value to use the component. Take the Input component as an example:

// Component provider
function Input({ defaultValue }) {
  returnFunction Demo() {return <input defaultValue={1} />}Copy the code

In a controlled component, the display and change of values are taken over by the component’s state and setState, respectively. Using the Input component as an example:

// Component provider
function Input() {
  const [value, setValue] = React.useState(1)
  return <input value={value} onChange={e= >SetValue (e.target.value)} />} // call function Demo() {return<Input />
}
Copy the code

An interesting question arises: Is the Input component controlled or uncontrolled? We can even change the code slightly to the way was originally called:

// Component provider
function Input({ defaultValue }) {
  const [value, setValue] = React.useState(defaultValue)
  return<input value={value} onChange={e => setValue(e.target.value)} />} // function Demo() {return <input defaultValue={1} /> }Copy the code

Although the Input component itself is a controlled component at this point, the caller loses control of changing the value of the Input component, so the Input component is an uncontrolled component to the caller. It is worth mentioning that invoking a controlled component in the same way that an uncontrolled component is used is an anti-pattern, and its drawbacks will be examined below.

How do you make an Input component a controlled component for both the component provider and the caller? The provider can transfer control to the following code: codesandBox:

// Component provider
function Input({ value, onChange }) {
  return<input value={value} onChange={onChange} />} function Demo() {const [value, setValue] = React.useState(1) return <Input value={value} onChange={e => setValue(e.target.value)} /> }Copy the code

After deducting the code above, the following is summarized: The boundary between controlled and uncontrolled components depends on whether the current component has control over changes to the values of its children. If so, the child component is a controlled component of the current component; If not, the child is an uncontrolled component of the current component.

Scope of functions

Controlled components give callers more customization functions than uncontrolled components, based on the caller’s perception that they have control over controlled components. This idea is similar to the open/closed principle in software development, and the Inversion of Control, which has benefited the author a lot, is also a similar idea.

With the empowerment of controlled components, such as the Input component, callers can be more free to impose validation restrictions on values, for example, or to perform additional logic when values change.

// Component provider
function Input({ value, onChange }) {
  return<input value={value} onChange={onChange} />} function Demo() {const [value, SetValue] = react.usestate (1) return <Input value={value} onChange={e => if (/\D/.test(e.target.value)) return setValue(e.target.value)} /> }Copy the code

Therefore, considering the expansibility and generality of basic components, the functions of controlled components are broader than those of uncontrolled components. Therefore, it is recommended to use controlled components to build basic components first.

Anti-pattern — Invoke a controlled component in the same way that an uncontrolled component is used

What are anti-patterns in the first place? I summarize this as a pattern that increases the probability of the occurrence of hidden bugs, which is the antithesis of best practice. Using antipatterns requires more effort to avoid potential bugs. The website also has a good summary of anti-patterns.

Why is it an anti-pattern to invoke a controlled component in the same way that an uncontrolled component is used? Look at the first line of code in the Input component, which assigns defaultValue to value. Assigning props to state increases the probability of some hidden bug.

For example, in the scenario of switching navigation bars, if the defaultValue passed into the components in the two navigation bars happens to be the same value, the state value of the Input in navigation 1 will be brought to navigation 2 during the navigation switch, which will obviously confuse the user. codesandbox

// Component provider
function Input({ defaultValue }) {
  / / the pattern
  const [value, setValue] = React.useState(defaultValue);
  React.useEffect((a)= > {
    setValue(defaultValue);
  }, [defaultValue]);
  return<input value={value} onChange={e => setValue(e.target.value)} />; Function Demo({defaultValue}) {return <Input defaultValue={defaultValue} />; } function App() { const [tab, setTab] = React.useState(1); return ( <> {tab === 1 ? <Demo defaultValue={1} /> : <Demo defaultValue={1} />} <button onClick={() => (tab === 1 ? SetTab (2) : setTab(1))}> Toggle Tab </button> </>); }Copy the code

How can you avoid using this antipattern while effectively solving the problem? The authorities offer two better solutions and leave them to ponder.

  1. Approach 1: Use fully controlled components (preferred)
  2. Method 2: Use a completely uncontrolled component + key

Welcome to Personal Blog