Skip to content

Instantly share code, notes, and snippets.

@anthonybrown
Forked from sastraxi/DebouncedProp.jsx
Created March 22, 2018 02:46
Show Gist options
  • Save anthonybrown/b504d8afc6801e3b9107a97de132e889 to your computer and use it in GitHub Desktop.
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
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