简体   繁体   中英

Correct way to throttle HTTP calls based on state in redux and react

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.

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))
}

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];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM