React redux api polling every x seconds

I’ve got this working but i’m after a more ‘best practice way’.

its using the https://icanhazdadjoke api to display a random joke that gets updated every x seconds. is there a better way of doing this?

eventually i want to add stop, start, reset functionality and feel this way might not be the best.

Any middleware i can use?

Redux actions

// action types
import axios from 'axios';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';


function fetchJoke() {
  return {
    type: FETCH_JOKE
  };
}

function fetchJokeSuccess(data) {
  return {
    type: FETCH_JOKE_SUCCESS,
    data
  };
}

function fetchJokeFail(error) {
  return {
    type: FETCH_JOKE_FAILURE,
    error
  };
}

export function fetchJokeCall(){
  return function(dispatch){
    dispatch(fetchJoke());
    return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
    .then(function(result){
      dispatch(fetchJokeSuccess(result.data))
    })
    .catch(error => dispatch(fetchJokeFail(error)));
  }
}

Redux reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE} from '../actions';

const defaultStateList = {
  isFetching: false,
  items:[],
  error:{}
};

const joke = (state = defaultStateList, action) => {
  switch (action.type){
  case FETCH_JOKE:
    return {...state, isFetching:true};
  case FETCH_JOKE_SUCCESS:
    return {...state, isFetching:false, items:action.data};
  case FETCH_JOKE_FAILURE:
    return {...state, isFetching:false, error:action.data};
  default:
    return state;
  }
};

const rootReducer = combineReducers({
  joke
});

export default rootReducer;

Joke component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchJokeCall } from '../actions';


class Joke extends Component {
  componentDidMount() {
    this.timer = setInterval(()=>  this.props.fetchJokeCall(), 1000);
  }
  componentWillUnmount() {
    clearInterval(this.timer)
    this.timer = null;
  }
  render() {
    return (
      <div>
        {this.props.joke.joke}
      </div>
    );
  }
}

Joke.propTypes = {
  fetchJokeCall: PropTypes.func,
  joke: PropTypes.array.isRequired
};

function mapStateToProps(state) {
  return {
    joke: state.joke.items,
    isfetching: state.joke.isFetching
  };
}

export default connect(mapStateToProps, { fetchJokeCall })(Joke);

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Redux-Sagas is better and we are using it in our applications as well, this is how you can poll using Redux-Sagas

Just to give you an idea this is how you can do it, You also need to understand how Redux-Sagas work

Action

export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
export const START_POLLING = 'START_POLLING';
export const STOP_POLLING = 'STOP_POLLING';

function startPolling() {
      return {
        type: START_POLLING
      };
    }

function stopPolling() {
      return {
        type: STOP_POLLING
      };
    }

function fetchJoke() {
  return {
    type: FETCH_JOKE
  };
}

function fetchJokeSuccess(data) {
  return {
    type: FETCH_JOKE_SUCCESS,
    data
  };
}

function fetchJokeFail(error) {
  return {
    type: FETCH_JOKE_FAILURE,
    error
  };
}

Reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';

const defaultStateList = {
  isFetching: false,
  items:[],
  error:{},
  isPolling: false,
};

const joke = (state = defaultStateList, action) => {
  switch (action.type){
  case FETCH_JOKE:
    return {...state, isFetching:true};
  case FETCH_JOKE_SUCCESS:
    return {...state, isFetching:false, items:action.data};
  case FETCH_JOKE_FAILURE:
    return {...state, isFetching:false, error:action.data};
  case START_POLLING:
    return {...state, isPolling: true};
  case STOP_POLLING:
    return {...state, isPolling: false};
  default:
    return state;
  }
};

const rootReducer = combineReducers({
  joke
});

export default rootReducer;

Sagas
import { call, put, takeEvery, takeLatest, take, race } from 'redux-saga/effects'

import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';

import axios from 'axios';



function delay(duration) {
  const promise = new Promise(resolve => {
    setTimeout(() => resolve(true), duration)
  })
  return promise
}

function* fetchJokes(action) {
  while (true) {
    try {
      const { data } = yield call(() => axios({ url: ENDPOINT }))
      yield put({ type: FETCH_JOKE_SUCCESS, data: data })
      yield call(delay, 5000)
    } catch (e) {
      yield put({ type: FETCH_JOKE_FAILURE, message: e.message })
    }
  }
}

function* watchPollJokesSaga() {
  while (true) {
    const data = yield take(START_POLLING)
    yield race([call(fetchJokes, data), take(STOP_POLLING)])
  }
}

export default function* root() {
  yield [watchPollJokesSaga()]
}

You can also use Redux-Observable, if you want to get more into this read this article

Method 2

I’ve been working on pretty much the same problem, except that I wasn’t concerned about starting and stopping the poll. For some reason the while loop kept freezing my app so I dispensed of it and instead set up my saga like this.

import { all, takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';

import { API_CALL_REQUEST, API_CALL_SUCCESS, API_CALL_FAILURE, API_CALL_FETCHED } from 
'../actions/giphy';

function apiFetch() {
  let randomWord = require('random-words');
  let API_ENDPOINT = `https://api.giphy.com/v1/gifs/search? 
                   api_key=MYKEY&q=${randomWord()}&limit=12`;
  return axios({
    method: "get",
    url: API_ENDPOINT
 });
}

export function* fetchImages() {
  try {
   const res = yield call(apiFetch)
   const images = yield res.data
   yield put({type: API_CALL_SUCCESS, images})

} catch (e) {
  yield put({type: API_CALL_FAILURE, e})
  console.log('Error fetching giphy data')
 }
}

export default function* giphySaga() {
  yield all([
    takeLatest(API_CALL_REQUEST, fetchImages),
 ]);
}

Then inside my component I added this.
 componentDidMount() {
   this.interval = setInterval(() => {
   this.props.dispatch({type: 'API_CALL_REQUEST'});
   }, 5000);
  }

componentWillUnmount() {
  clearInterval(this.interval)
}

It’s working, but would like some feedback on how this could be possibly improved.

Method 3

Here’s a poor man’s way. I don’t think it’s the best way but it doesn’t require any extra library.

Actions

// action types
import axios from 'axios';

export const START_POLLING_JOKE = 'START_POLLING_JOKE';
export const STOP_POLLING_JOKE = 'STOP_POLLING_JOKE';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';

const defaultPollingInterval = 60000

function startPollingJoke(interval = defaultPollingInterval) {
  return function (dispatch) {
    const fetch = () => dispatch(fetchJoke())
    dispatch({
      type: START_POLLING_JOKE,
      interval,
      fetch,
    })
  }
}

function stopPollingJoke() {
  return {
    type: STOP_POLLING_JOKE
  }
}

function fetchJoke() {
  return {
    type: FETCH_JOKE
  };
}

function fetchJokeSuccess(data) {
  return {
    type: FETCH_JOKE_SUCCESS,
    data
  };
}

function fetchJokeFail(error) {
  return {
    type: FETCH_JOKE_FAILURE,
    error
  };
}

export function pollJokeCall(interval = defaultPollingInterval) {
  return function (dispatch) {
    dispatch(fetchJoke())
    dispatch(startPollingJoke(interval))
  }
}

export function fetchJokeCall() {
  return function(dispatch){
    dispatch(fetchJoke());
    return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
    .then(function(result){
      dispatch(fetchJokeSuccess(result.data))
    })
    .catch(error => dispatch(fetchJokeFail(error)));
  }
}

Reducers
import {combineReducers} from 'redux';
import {
  START_POLLING_JOKE,
  STOP_POLLING_JOKE,
  FETCH_JOKE, 
  FETCH_JOKE_SUCCESS, 
  FETCH_JOKE_FAILURE,
} from '../actions';

const defaultStateList = {
  isFetching: false,
  items:[],
  error:{},
  pollingId: null,
  polling: false,
};

const joke = (state = defaultStateList, action) => {
  switch (action.type){
  case START_POLLING_JOKE: 
    clearInterval(state.pollingId)
    return {
      ...state,
      polling: true,
      pollingId: setInterval(action.fetch, action.interval),
    }
  }
  case STOP_POLLING_JOKE: 
    clearInterval(state.pollingId)
    return {...state, polling: false, pollingId: null}
  case FETCH_JOKE:
    return {...state, isFetching:true};
  case FETCH_JOKE_SUCCESS:
    return {...state, isFetching:false, items:action.data};
  case FETCH_JOKE_FAILURE:
    return {...state, isFetching:false, error:action.data};
  default:
    return state;
  }
};

const rootReducer = combineReducers({
  joke
});

export default rootReducer;

Component (might have a bug because I’m not used to class components)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { pollJokeCall, stopPollingJoke } from '../actions';


class Joke extends Component {
  componentDidMount() {
    this.props.pollJokeCall()
  }
  componentWillUnmount() {
    this.props.stopPollingJoke()
  }
  render() {
    return (
      <div>
        {this.props.joke.joke}
      </div>
    );
  }
}

Joke.propTypes = {
  pollJokeCall: PropTypes.func,
  stopPollingJoke: PropTypes.func,
  joke: PropTypes.array.isRequired,
};

function mapStateToProps(state) {
  return {
    joke: state.joke.items,
    isfetching: state.joke.isFetching
  };
}

export default connect(mapStateToProps, { pollJokeCall, stopPollingJoke })(Joke);

Method 4

I have made a small (5kb gzipped) helper to create polling based on redux-thunk store. The idea is to have a logic to prevent registering the same polling twice, have callbacks between iterations and more.

https://www.npmjs.com/package/redux-polling-thunk

Method 5

redux-saga is great and I’ve been using this with redux. It provides a great api to do things like delay, polling, throttling, race conditions, task cancellations. So using redux-saga, you can add a watcher whcih will keep on pooling

function* pollSagaWorker(action) {
  while (true) {
    try {
      const { data } = yield call(() => axios({ url: ENDPOINT }));
      yield put(getDataSuccessAction(data));
      yield call(delay, 4000);
    } catch (err) {
      yield put(getDataFailureAction(err));
    }
  }
}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x