-
-
Save anthonybrown/b504d8afc6801e3b9107a97de132e889 to your computer and use it in GitHub Desktop.
Store quickly-changing state directly in Redux (e.g. the value of a textarea) without a bajillion actions being dispatched
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import _ from 'lodash'; | |
/** | |
* Protect a heavyweight setter by caching values locally. | |
* Allows functional controlled components with e.g. a redux backing store, | |
* without dispatching actions on every keypress. | |
* | |
* @param {*} valueProp the value to cache (e.g. a key from mapStateToProps) | |
* @param {*} setterProp the heavyweight setter to protect (e.g. a key from mapDispatchToProps) | |
* @param {*} debounceTime maximum time to delay calls to props.setterProp, default 500ms | |
*/ | |
export const debouncedProp = (valueProp, setterProp, debounceTime = 500) => | |
function (WrappedComponent) { | |
return class DP extends React.Component { | |
// N.B. we keep our own "version" of the value in state to give instant feedback | |
// to the component without using the relatively-heavy setter (e.g. redux action) | |
state = { | |
[valueProp]: undefined, | |
}; | |
constructor(props) { | |
super(props); | |
this.callSetter = _.debounce(this.callSetter, debounceTime); | |
this.state = { | |
[valueProp]: props[valueProp], | |
}; | |
} | |
// we want to respond to external events that will modify our value, | |
// but by dispatching our debounced change to e.g. redux, we'll asynchronously | |
// receive the props we just set later on. this can cause the input value | |
// to revert to a previously-typed value. So we'll keep track of the values | |
// that we've dispatched and ignore them when they come back in | |
// via. componentWillReceiveProps. | |
pendingValueChanges = []; | |
componentWillReceiveProps = (nextProps) => { | |
if (nextProps[valueProp] !== this.state[valueProp]) { | |
const index = this.pendingValueChanges.indexOf(nextProps[valueProp]); | |
if (index === -1) { | |
this.setState({ | |
[valueProp]: nextProps[valueProp], | |
}); | |
} else { | |
// ignore this query change (once!) as we were the source | |
this.pendingValueChanges = [ | |
...this.pendingValueChanges.slice(0, index), | |
...this.pendingValueChanges.slice(index + 1), | |
]; | |
} | |
} | |
}; | |
valueChanged = (value) => { | |
this.setState({ [valueProp]: value }); | |
this.callSetter(value); | |
}; | |
callSetter = (value) => { | |
this.pendingValueChanges = [...this.pendingValueChanges, value]; | |
this.props[setterProp](value); | |
}; | |
render = () => | |
<WrappedComponent | |
{...this.props} | |
{...{ | |
[valueProp]: this.state[valueProp], | |
[setterProp]: this.valueChanged, | |
}} | |
/>; | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment