Search code examples
reactjsreduxpolling

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

Solution

  • 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