Reputation: 2576
What I need:
A controlled component that is an input[type="search"]
. After say, 1 second of no changes to that component I want to send out an HTTP call to execute that search and have the results shown in another component.
How I've done it:
Whenever I call dispatch(setSearch(str));
I make a call to dispatch(displaySearch(false));
and then _.throttle
a call to dispatch(displaySearch(true));
It feels to me that doing this sort of work in the component is incorrect, but I can't think of a way to do this in a reducer in redux.
Upvotes: 12
Views: 5605
Reputation: 2464
Associate delay
and takeLatest
:
import { all, takeLatest, call } from 'redux-saga/effects';
import { delay } from 'redux-saga';
function* onSearch(action) {
yield call(delay, 1000); // blocks until a new action is
// triggered (takeLatest) or until
// the delay of 1s is over
const results = yield call(myFunction);
}
function* searchSagas() {
yield all([
// ...
takeLatest(ON_SEARCH, onSearch),
]);
}
export default [searchSagas];
Upvotes: 3
Reputation: 12420
You have different options to solve this.
1. Debounce your action at a component level
This is the simplest approach. When the input triggers a change, it calls
a debounced version of setSearch
delaying the server call.
import * as React from "react"
import {connect} from "react-redux"
import {setSearch} from "./actions"
export default connect(
null,
function mapDispatchToProps(dispatch) {
const setSearch_ = _.debounce(q => dispatch(setSearch(q)), 1000)
return () => ({setSearch: setSearch_})
}
)(
function SearchForm(props) {
const {setSearch} = props
return (
<input type="search" onChange={setSearch} />
)
}
)
2. Debounce using redux-saga
This approach requires more boilerplate but gives you a lot more control over
the workflow. Using a saga we intercept the SET_SEARCH
action, debounce it,
call the API then dispatch a new action containing the results.
import {call, cancel, fork, put, take} from "redux-saga/effects"
import {setSearchResults} from "./actions"
import {api} from "./services"
import {delay} from "./utils"
export default function* searchSaga() {
yield [
// Start a watcher to handle search workflow
fork(watchSearch)
]
}
function* watchSearch() {
let task
// Start a worker listening for `SET_SEARCH` actions.
while (true) {
// Read the query from the action
const {q} = yield take("SET_SEARCH")
// If there is any pending search task then cancel it
if (task) {
yield cancel(task)
}
// Create a worker to proceed search
task = yield fork(handleSearch, q)
}
}
function* handleSearch(q) {
// Debounce by 1s. This will lock the process for one second before
// performing its logic. Since the process is blocked, it can be cancelled
// by `watchSearch` if there are any other actions.
yield call(delay, 1000)
// This is basically `api.doSearch(q)`. The call should return a `Promise`
// that will resolve the server response.
const results = yield call(api.doSearch, q)
// Dispatch an action to notify the UI
yield put(setSearchResults(results))
}
Upvotes: 9