Rxjs debounce on react text input component
Rxjs debounce on react text input component
I have the following react component
<input
className=styles.incSrchTextBox
type="text" name="search" placeholder="Search.."
onChange=this.onChange
/>
onChange(e)
const newText = e.target.value;
console.log(newText);
this.setState( searchText: newText );
How do I use debounce on rxjs on this?
3 Answers
3
You will need to cretae observable from change events(for example using Subject) and then debounce on that.
Here is the fully featured example for you:
class Search extends React.Component
constructor(props)
super(props);
this.state =
search: '',
debounced: '',
;
this.onSearch$ = new Rx.Subject();
this.onSearch = this.onSearch.bind(this);
componentDidMount()
this.subscription = this.onSearch$
.debounceTime(300)
.subscribe(debounced => this.setState( debounced ));
componentWillUnmount()
if (this.subscription)
this.subscription.unsubscribe();
onSearch(e)
const search = e.target.value;
this.setState( search );
this.onSearch$.next(search);
render()
const search, debounced = this.state;
return (
<div>
<input type="text" value=search onChange=this.onSearch />
<div>debounced value: debounced</div>
</div>
);
ReactDOM.render(
<Search />,
document.getElementById('root')
);
<script src="https://unpkg.com/rxjs@5.4.0/bundles/Rx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
yes, but it is not a good idea to work with DOM directly when you already have React
– Oles Savluk
Jun 1 '17 at 23:25
This would be a good use case for Refract!
The first step would be to pull the input out into a separate component:
const Input = ( onChange, value ) => (
<input type="text" value=value onChange=onChange />
)
Next step would be to wrap this component with Refract's withEffects
higher-order component, with a handler
and an aperture
to handle the side-effects like this:
withEffects
handler
aperture
import withEffects from 'refract-rxjs'
import debounceTime from 'rxjs/operators'
const Input = ( onChange, value ) => (
<input type="text" value=value onChange=onChange />
)
const aperture = () => component =>
component.observe('value').pipe(debounceTime(300))
const handler = ( onUpdate ) => value => onUpdate(value)
const DebouncedInput = withEffects(handler)(aperture)(Input)
An aperture
lets you observe your component's props. In this case, it would make sense to observe the value
prop - every time the value
changes, the component.observe('value')
stream gets a new value.
aperture
value
value
component.observe('value')
The handler
is a function called with each value output by the aperture's stream. In this case, the debounced value is passed straight through to a new prop called onUpdate
.
handler
onUpdate
Both apertures and handlers are explained in detail in the docs - Observing React introduces apertures, and Handling Effects explains handlers.
As an example of how you would use this:
class Search extends React.Component
state = debounced: '', search: ''
onSearch = e => this.setState( search: e.target.value )
onUpdate = debounced => this.setState( debounced )
render()
return (
<div>
<DebouncedInput
type="text"
value=this.state.search
onChange=this.onSearch
onUpdate=this.onUpdate
/>
<div>debounced value: debounced</div>
</div>
)
With this code, the text DebouncedInput
would display the user's input instantly (which is ideal for UX), while debouncing the side-effect of calling the onUpdate
callback. It would then be trivial to expose this onUpdate
to components which consume the Search
component!
DebouncedInput
onUpdate
onUpdate
Search
I agree with the example by Oles Savluk. In addition, I would extract the Subject logic out of the component. It doesn't need to live inside the component, as it has no state, and I think this also makes the component easier to understand.
Also: The example is updated to use RxJS 6.2.2
const Subject = rxjs;
const debounceTime = rxjs.operators;
const onSearch$ = new rxjs.Subject().pipe(
debounceTime(300)
);
class Search extends React.Component
constructor(props)
super(props);
this.state =
search: '',
debounced: '',
;
componentDidMount()
this.subscription = onSearch$.subscribe(
debounced => this.setState( debounced )
);
componentWillUnmount()
if (this.subscription)
this.subscription.unsubscribe();
onSearch = (e) =>
const search = e.target.value;
this.setState( search );
onSearch$.next(search);
render()
const search, debounced = this.state;
return (
<div>
<input type="text" value=search onChange=this.onSearch />
<div>debounced value: debounced</div>
</div>
);
ReactDOM.render(
<Search />,
document.getElementById('root')
);
<script src="https://unpkg.com/rxjs@6.2.2/bundles/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
cant I do something like this without creating a subject const example = Rx.Observable .fromEvent(input, 'keyup') .map(i => i.currentTarget.value); //wait .5s between keyups to emit current value //throw away all other values const debouncedInput = example.debounceTime(500);
– tmp dev
Jun 1 '17 at 22:30