FBI WARNING: this may be an anti-pattern in regard to react's stateless design.

As is known to all, there're a lot of web components that can be mutated by user interactions, such as <input>, <select>, or the rich editor I am using now. These components are inconspicuous in daily development - we can easily modify the value by typing something in it or set the value property. However, as React comes with one-way data binding, these components seem to be out of control:

  1. A <Input> that maintains its own state of value can't be mutated from outside;
  2. A <Input> of which value is set via props can't update without outside control.

Finally, React put forward the concept of Controlled Components and Uncontrolled Components.

Controlled Components

A controlled <input> has a value prop. Rendering a controlled <input> will reflect the value of the value prop.

A Controlled component does not maintain its own internal state; the component renders purely based on props.

That is to say, if we have a <input> with value set from props, it will keep showing props.value in spite of your inputs. In other words, your component is read-only.

It's amazing that some popular components are performing this way. Take a look at react-toolbox playground, if we remove the value property from <Dropdown />, it turns into a dead dropdown - who will fall in love with a dead guy?

While working with controlled components, you should always pass a value prop and register an onChange handler to make them alive, and as a result, the state of upper components will be complex and chaotic.

Uncontrolled Components

An <input> without a value property is an uncontrolled component. Any user input will be immediately reflected by the rendered element.

An uncontrolled component maintains its own internal state.

Well, now we can act more like native ones. But wait! How can I change the value of input like I used to do in vanilla js input.value = xxx?

Sadly you have no way to change the value from outside, because it's uncontrolled.

Hybrid

So why not build a component that is both controlled and uncontrolled? We can get some principles according to React's definition of (un)controlled components:

Principle Ⅰ

props.value always has a higher priority than internal state.value.

When props.value is set, we should always render it instead of state.value, so we can define a displayValue getter property:

get displayValue() {  
  return this.props[key] !== undefined ?
    this.props[key] : this.state[internalKey];
}

Then in render function:

render() {  
  return (<div>{this.displayValue}</div>);
}

Principle Ⅱ

Any change on component should be synced to internal state.value, then request an update via props.onChange.

Sync value to state.value ensures components will be able to render newest value when it's uncontrolled. Request an outside update tells upper component to perform a change on props.value, thus the controlled components can also render the right value.

handleChange(newVal) {  
  if (newVal === this.state.value) return;

  this.setState({
    value: newVal,
  }, () => {
    this.props.onChange && this.props.onChange(newVal);
  });
}

Principle Ⅲ

Reflect props.value to state.value when component receives new props.

It's important to sync between props.value and state.value in order to amend the internal value and perform correct operations in handleChange:

componentWillReceiveProps(nextProps) {  
  const controlledValue = nextProps.value;

  if (controlledValue !== undefined &&
      controlledValue !== this.state.value
  ) {
    this.setState({
      value: controlledValue,
    });
  }
}

Principle Ⅳ

Update components if prior value has changed.

This prevents components from unnecessary re-rendering, for example, a controlled component shouldn't fire a re-render when internal state.value changed.

shouldComponentUpdate(nextProps, nextState) {  
  if (nextProps.value !== undefined) {
    // controlled, use `props.value`
    return nextProps.value !== this.props.value;
  }

  // uncontrolled, use `state.value`
  return nextState.value !== this.state.value;
}

The workaround

With all the above principles, we can create a decorator like this gist. Use it like:

@hybridCtrl
class App extends React.Component {  
  static propTypes = {
    value: React.PropTypes.any,
  }

  state = {
    _value: '',
  }

  mapPropToState(controlledValue) {
    // your can do some transformations from `props.value` to `state._value`
  }

  handleChange(newVal) {
    // it's your duty to handle change events and dispatch `props.onChange`
  }
}

Conclusion

  1. Why we need hybrid?

    We should create components that are both controlled and uncontrolled, just like the native ones.

  2. The main idea of hybrid?

    Maintaining both props.value and state.value. The props.value has a higher priority in display, state.value reflects actual value of the components.