React provides two types of text input fields, controlled and uncontrolled components. The value of a controlled component is managed by React and requires that the value property of the component be set and that the component have a handler for managing onChange events. Below is an example of a controlled input field you might typically see used in a form.

Controlled input field import React from 'react'; export default class ControlledInput extends React.Component { render() { return ( <fieldset> <label>{this.props.label}</label> <input type='text' ref='input' name={this.props.name} value={this.props.value} onChange={this.handleChange.bind(this)}/> </fieldset> ); } handleChange() { this.props.onChange(this.refs.input.value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React from 'react' ; export default class ControlledInput extends React . Component { render ( ) { return ( < fieldset > < label > { this . props . label } < / label > < input type = 'text' ref = 'input' name = { this . props . name } value = { this . props . value } onChange = { this . handleChange . bind ( this ) } / > < / fieldset > ) ; } handleChange ( ) { this . props . onChange ( this . refs . input . value ) ; } }

Controlled components therefore need to have the value of the field managed somehow. There are two commonly used approaches for this; managing the value inside the component state and managing it within a store when implementing a Flux architecture. The Flux approach can lead to performance issues in the user interface when intermediary actions don’t allow the value to update immediately. We’ll see an exaggerated example here with a username field that contacts a server to check if the username is already in use. The call to the server needs to happen when the field value changes but we don’t want to wait for the response before we update the value in the store and propagate that change to the user interface. Let’s see an example of this using Redux.

app.js import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ControlledInput from 'controlled-input'; import { valueChange } from 'actions'; function mapStateToProps(state) { return { value: state.value }; } function mapDispatchToProps(dispatch) { return { onValueChange: bindActionCreators(valueChange, dispatch) }; } @connect(mapStateToProps, mapDispatchToProps) export default class App extends React.Component { render() { return ( <form className='test-form'> <ControlledInput value={this.props.value} name='username' label='Username' onChange={this.props.onValueChange} /> </form> ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from 'react' ; import { connect } from 'react-redux' ; import { bindActionCreators } from 'redux' ; import ControlledInput from 'controlled-input' ; import { valueChange } from 'actions' ; function mapStateToProps ( state ) { return { value : state . value } ; } function mapDispatchToProps ( dispatch ) { return { onValueChange : bindActionCreators ( valueChange , dispatch ) } ; } @ connect ( mapStateToProps , mapDispatchToProps ) export default class App extends React . Component { render ( ) { return ( < form className = 'test-form' > < ControlledInput value = { this . props . value } name = 'username' label = 'Username' onChange = { this . props . onValueChange } / > < / form > ) ; } }

This is the top level (Smart) component that manages the application state. When the value of the controlled input component changes the onValueChange action is created and dispatched. This action is asynchronous and causes severe problems with updates to the field. The value is not updated immediately in the store so the field does not show the new value when typing, this also means on subsequent key presses the wrong value is read and previous updates are lost. The code for the action which simulates a call to the server is shown below.

Asynchronous action creator const ActionTypes = { VALUE_CHANGE: 'value-change' }; const checkUserExists = function(value) { return (dispatch) => { setTimeout(() => { dispatch({ type: ActionTypes.VALUE_CHANGE, existingUser: Math.random() > 0.5, newValue: value }); }, 500); }; } export function valueChange(value) { return checkUserExists(value); } export default ActionTypes; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const ActionTypes = { VALUE_CHANGE : 'value-change' } ; const checkUserExists = function ( value ) { return ( dispatch ) = > { setTimeout ( ( ) = > { dispatch ( { type : ActionTypes . VALUE_CHANGE , existingUser : Math . random ( ) > 0.5 , newValue : value } ) ; } , 500 ) ; } ; } export function valueChange ( value ) { return checkUserExists ( value ) ; } export default ActionTypes ;

Let’s first look at fixing the issue using the component state so that the value updates are not lost and are reflected in the UI immediately. This approach goes against the grain of having dumb components that only require property updates from a single source of truth, the Flux store.

Value managed with state import React from 'react'; export default class ControlledInput extends React.Component { constructor(props) { super() this.state = { value: props.value }; } componentWillReceiveProps(newProps) { this.setState({value: newProps.value}); } render() { return ( <fieldset> <label>Controlled input</label> <input type='text' ref='input' name='controlled' value={this.state.value} onChange={this.handleChange.bind(this)}/> </fieldset> ); } handleChange() { const value = this.refs.input.value; this.setState({value}) this.props.onChange(value); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import React from 'react' ; export default class ControlledInput extends React . Component { constructor ( props ) { super ( ) this . state = { value : props . value } ; } componentWillReceiveProps ( newProps ) { this . setState ( { value : newProps . value } ) ; } render ( ) { return ( < fieldset > < label > Controlled input < / label > < input type = 'text' ref = 'input' name = 'controlled' value = { this . state . value } onChange = { this . handleChange . bind ( this ) } / > < / fieldset > ) ; } handleChange ( ) { const value = this . refs . input . value ; this . setState ( { value } ) this . props . onChange ( value ) ; } }

These changes allow the value to be set initially, be updated by the onChange events in the user interface and have the value updated in subsequent renders from external property changes. But this isn’t what we want in our Flux architecture now, is it?

A better solution to the problem is to identify the two separate actions early and create two distinct data flows. One action to update the value in the store immediately and a second action to make the call to the server. Let’s see what that looks like in the top level component.

Separate actions import React from 'react'; import debounce from 'lodash/function/debounce'; import { connect } from 'react-redux'; import ControlledInput from 'controlled-input'; import { syncAction, asyncAction } from 'actions'; function mapStateToProps(state) { return { value: state.value, message: state.message }; } function mapDispatchToProps(dispatch) { const debouncedAsync = debounce(value => dispatch(asyncAction(value)), 200); return { onValueChange: value => { dispatch(syncAction(value)) debouncedAsync(value); } }; } @connect(mapStateToProps, mapDispatchToProps) export default class App extends React.Component { render() { return ( <form className='test-form'> <ControlledInput value={this.props.value} name='username' label='User name' onChange={this.props.onValueChange} /> <p>{this.props.message}</p> </form> ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import React from 'react' ; import debounce from 'lodash/function/debounce' ; import { connect } from 'react-redux' ; import ControlledInput from 'controlled-input' ; import { syncAction , asyncAction } from 'actions' ; function mapStateToProps ( state ) { return { value : state . value , message : state . message } ; } function mapDispatchToProps ( dispatch ) { const debouncedAsync = debounce ( value = > dispatch ( asyncAction ( value ) ) , 200 ) ; return { onValueChange : value = > { dispatch ( syncAction ( value ) ) debouncedAsync ( value ) ; } } ; } @ connect ( mapStateToProps , mapDispatchToProps ) export default class App extends React . Component { render ( ) { return ( < form className = 'test-form' > < ControlledInput value = { this . props . value } name = 'username' label = 'User name' onChange = { this . props . onValueChange } / > < p > { this . props . message } < / p > < / form > ) ; } }

And the separate action creators.

Separate action creators const ActionTypes = { SYNC_UPDATE: 'sync-update', ASYNC_UPDATE: 'async-update' }; const checkUserExists = function(value) { return (dispatch) => { setTimeout(() => { dispatch({ type: ActionTypes.ASYNC_UPDATE, existingUser: Math.random() > 0.5 }); }, 500); }; } export function asyncAction(value) { return checkUserExists(value); } export function syncAction(value) { return { type: ActionTypes.SYNC_UPDATE, newValue: value }; } export default ActionTypes; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const ActionTypes = { SYNC_UPDATE : 'sync-update' , ASYNC_UPDATE : 'async-update' } ; const checkUserExists = function ( value ) { return ( dispatch ) = > { setTimeout ( ( ) = > { dispatch ( { type : ActionTypes . ASYNC_UPDATE , existingUser : Math . random ( ) > 0.5 } ) ; } , 500 ) ; } ; } export function asyncAction ( value ) { return checkUserExists ( value ) ; } export function syncAction ( value ) { return { type : ActionTypes . SYNC_UPDATE , newValue : value } ; } export default ActionTypes ;

You can see that with these changes we are also able to debounce the existing username check so that it only raises the action to call the server after 200ms of inactivity preventing unnecessary network requests.

You can find the full code example on Github and please feel free to leave any questions in the comments.