Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

reactjs

redux

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.

like image 588
Dave Avatar asked Apr 13 '16 15:04

Dave


People also ask

What is the only way to trigger a state change 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.

What is throttling in Reactjs?

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.

Why we use debounce in react?

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.


2 Answers

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];
like image 159
Made in Moon Avatar answered Sep 20 '22 14:09

Made in Moon


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))
}
like image 36
Florent Avatar answered Sep 19 '22 14:09

Florent