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.
dispatch(action) Dispatches an action. This is the only way to trigger a state change. The store's reducing function will be called with the current getState() result and the given action synchronously.
If we decide to prevent the second process from happening by making sure that our function can only run once in a given interval, that would be throttling.
If we type the first character, that is 8, we will send request to the backend server. Then we type 0, and we will send another request to the server, and so on. This calls the API so many times, and in turn overuses the requests. So, to prevent this, we use something called a debounce function.
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];
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))
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With