I’ve been working on this all morning, and now that I’ve finally gotten something working I’m going to jot down some notes and maybe add more detail later.

Problem

Say you have a controlled <textarea> with an onChange event that’s hooked up to a Redux action. That Redux action in turn is handled via some sort of async middleware like redux-promise-middleware. The problem is that every single character press fires off an action, which initiates an API request, which has to resolve before the view updates. Time to debounce.*

Synchronous Logic w/ Made Up Times:

Type ‘Redux’ by pressing R-e-d-u-x with 10ms gaps between each key press and the reducer returning the value 5ms later. The chain of actions is:

export const doReduxAction = data => ({

type: 'DO_REDUX_ACTION',

data

}) // Values returning via props

'R' ==> 'Re' ==> 'Red' ==> 'Redu' ==> 'Redux

@~5ms @~15ms @~25ms @~35ms @~45ms

Attempted Solution 1: Debounce the Redux Action Dispatch (Bad)

import { connect } from 'react-redux'

import debounce from 'lodash/debounce'

import { doReduxAction } from './actions/myActions ... constructor(props) {

super(props)

this.doReduxAction = debounce(this.props.doReduxAction, 250)

}

This won’t work because a controlled component depends on the value coming back in IMMEDIATELY from the reducer to update the value. If you wait 250ms for the function to run you’re going to have to wait at least 250ms for the view to update, plus you’ll break the logic chaining of how the string is built one character at a time. So after 40ms of typing you’ll dispatch one action with {type: 'DO_REDUX_ACTION', data: 'x'}

Attempted Solution 2: Debounce the Component Method (Bad)

import { connect } from 'react-redux'

import debounce from 'lodash/debounce'

import { doReduxAction } from './actions/myActions ... doAction = val => this.props.doReduxAction(val)

doDebouncedAction = debounce(this.doAction, 250) ... <textarea

onChange={this.doDebouncedAction)

value={this.props.value} />

Same problem. Duh.

Solution 3: Debounce a New Component w/ Internal State

I’ll skip to the punchline here. The way that I’ve figured out to do it is to make sure that the <textarea> is injected from another component, say <MyTextarea> .

// MyTextrea.js

import debounce from 'lodash/debounce' class MyTextarea extends Component {

constructor(props){

super(props)

this.state = {

value: props.value

}

// debounce the passed in dispatch method

this.changed = debounce(this.props.changed, 250)

} // pass in the Redux action dispatcher and the

// returned value via props

static propTypes = {

changed: React.PropTypes.func.isRequired,

value: React.PropTypes.string.isRequired

} handleChange = e => {

// React event weirdness requires storing

// the synthetic event

const val = e.target.value

this.setState({ value: val }, () => {

this.changed(val)

})

} render() {

return (

<textarea

onChange={this.handleChange}

value={this.props.value}

)

}

}

It seems ugly to maintain internal state within a controlled component, but it’s the only way that I can think of to have the element respond appropriately to user input while waiting to run the Redux action. The danger here would be that internal state would at some point get out of sync w/ the injected prop value, but some code in componentWillReceiveProps could probably be performed to iron out the most likely bugs.

Anyone have a better pattern for this?