Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite recursion occurring with middleware #333

Closed
jbri7357 opened this issue Mar 31, 2016 · 1 comment
Closed

Infinite recursion occurring with middleware #333

jbri7357 opened this issue Mar 31, 2016 · 1 comment

Comments

@jbri7357
Copy link

I'm working on a webapp that's utilizing React with Redux. I have posted a similar message at the redux-thunk Github, but I think this might possibly be a more general issue with Redux middleware. I am currently using Redux-Thunk in a manner very similar to the example found here:
http://redux.js.org/docs/advanced/ExampleRedditAPI.html

When running my app, I see the following error in Chrome:

Uncaught RangeError: Maximum call stack size exceeded
(anonymous function) @ index.js:10
dispatch @ applyMiddleware.js:44
(anonymous function) @ index.js:12
dispatch @ applyMiddleware.js:44
(anonymous function) @ index.js:12
dispatch @ applyMiddleware.js:44
(anonymous function) @ index.js:12
dispatch @ applyMiddleware.js:44

Does anyone have any idea what might be the source of this infinite recursion?

actions.js:

import fetch from 'isomorphic-fetch'

export const SELECT_CUSTOMER = 'SELECT_CUSTOMER';
export const REQUEST_DATA = 'REQUEST_DATA';
export const RECEIVE_DATA = 'RECEIVE_DATA';
export const INVALIDATE_DATA = 'INVALIDATE_DATA';

export function selectCustomer(customer) {
  return {
    type: SELECT_CUSTOMER,
    customer
  }
}

export function invalidateData(customer, api) {
  return {
    type: INVALIDATE_DATA,
    customer,
    api
  }
}

function requestData(customer, api) {
  return {
    type: REQUEST_DATA,
    customer,
    api
  }
}

function receiveData(customer, api, data) {
  return {
    type: RECEIVE_DATA,
    customer: customer,
    api: api,
    data: data.children.map(child => child.data),
    receivedAt: Date.now()
  }
}

function performFetch(customer, requestBodyJson) {
  return fetch('http://localhost:8080/ediconsole/service', {
    method: 'POST',
    refreshInterval: 30000,
    headers: {
      'Content-Type': 'application/json'
    },
    body: requestBodyJson
  })
}

function fetchASNOverview(customer) {
  let requestBodyJson = JSON.stringify({
    id: '0',
    method: 'getASNOverview',
    params: [
      customer ? customer : 'ASN Overview'
    ]
  });
  return (dispatch) => {
    dispatch(requestData(customer))
    performFetch(requestBodyJson)
      .then(response => response.json())
      .then(json => dispatch(receiveData(customer, 'overview', json)));
  }
}

function fetchASNFailures(minDate, maxDate, customer) {
  let requestBodyJson = JSON.stringify({
    id: '0',
    method: 'getASNFailures',
    params: [minDate, maxDate,
      customer ? customer : 'ASN Overview'
    ]
  });
  return (dispatch) => {
    dispatch(requestData(customer))
    performFetch(requestBodyJson)
      .then(response => response.json())
      .then(json => dispatch(receiveData(customer, json)));
  }
}

function fetchASNDetails(date, status, customer) {
  let requestBodyJson = JSON.stringify({
    id: '0',
    method: 'getASNOverviewForDateAndStatus',
    params: [date, status, customer ? customer : 'ASN Overview']
  });
  return (dispatch) => {
    dispatch(requestData(customer))
    performFetch(requestBodyJson)
      .then(response => response.json())
      .then(json => dispatch(receiveData(customer, json)));
  }
}

function shouldFetchPosts(state, customer, api) {
  const customerDataFromSpecificApi = state[customer][api];

  if (!customerDataFromSpecificApi.data) {
    return true
  }
  if (customerDataFromSpecificApi.isFetching) {
    return false
  }
  return customerDataFromSpecificApi.didInvalidate
}

export function fetchOverviewDataIfNeeded(customer) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), customer, 'overview')) {
      return dispatch(fetchASNOverview(customer))
    }
  }
}

export function fetchFailuresDataIfNeeded(minDate, maxDate, customer) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), customer, 'failures')) {
      return dispatch(fetchASNFailures(minDate, maxDate, customer))
    }
  }
}

export function fetchDetailsDataIfNeeded(date, status, customer) {
  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), customer, 'details')) {
      return dispatch(fetchASNDetails(date, status, customer))
    }
  }
}

configureStore.js:

import {
  applyMiddleware,
  createStore
} from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducer';
import createLogger from 'redux-logger';

export default function configureStore(initialState) {
  const store = createStore(rootReducer,
    initialState,
    applyMiddleware(thunkMiddleware, createLogger())
  );

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('./reducer', () => {
      const nextReducer = require('./reducer').default
      store.replaceReducer(nextReducer)
    })
  }

  return store
}

reducer.js:

import {
  SELECT_CUSTOMER,
  REQUEST_DATA,
  RECEIVE_DATA,
  INVALIDATE_DATA
} from './actions';
import {
  routerReducer as routing
} from 'react-router-redux';
import {
  combineReducers
} from 'redux';


function selectedCustomer(state = 'ASN Overview', action) {
  switch (action.type) {
    case SELECT_CUSTOMER:
      return action.customer
    default:
      return state
  }
}

function data(state = {
  isFetching: false,
  didInvalidate: false,
  data: {}
}, action) {
  switch (action.type) {
    case INVALIDATE_DATA:
      return Object.assign({}, state, {
        [action.customer]: Object.assign({}, state, {
          didInvalidate: true
        })
      });

    case REQUEST_DATA:
      return Object.assign({}, state, {
        [action.customer]: Object.assign({}, state, {
          isFetching: true,
          didInvalidate: false
        })
      });

    case RECEIVE_DATA:
      return Object.assign({}, state, {
        [action.customer]: Object.assign({}, state, {
          isFetching: false,
          didInvalidate: false,
          data: action.data,
          lastUpdated: action.receivedAt
        })
      });
    default:
      return state;
  }
}


function asnDataByCustomer(state = createInitialState(), action) {
  switch (action.type) {
    case INVALIDATE_DATA:
    case RECEIVE_DATA:
    case REQUEST_DATA:
      return Object.assign({}, state, {
        [action.customer]: data(state[action.customer][action.api], action)
      })
    default:
      return state
  }
}

function createInitialState() {
  let initialState = {
      'ASN Overview': null,
      'Customer 1': null,
      'Customer 2': null,
      'Customer 3': null,
      'Customer 4': null
  };

  for (let key in initialState) {
    initialState[key] = Object.assign({}, {
      overview: null,
      failures: null,
      details: null
    });
  }

  return initialState;
}

const rootReducer = combineReducers({
  asnDataByCustomer,
  selectedCustomer,
  routing
});

export default rootReducer;

main.js

import React from 'react';
import {render} from 'react-dom';
import 'babel-polyfill';
import {Router, Route, IndexRoute, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux'
import configureStore from './redux/configureStore';
import {Provider} from 'react-redux';
import App from './components/App.jsx';
import HomePage from './components/pages/HomePage.jsx';
import NotFoundPage from './components/pages/NotFoundPage.jsx';
import CustomerPage from './components/pages/CustomerPage.jsx';

const store = configureStore();

const history = syncHistoryWithStore(browserHistory, store);

render((
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <IndexRoute component={HomePage}/>
        <Route path="customer" component={CustomerPage}/>
        <Route path="customer/:customerName" component={CustomerPage}/>
        <Route path="*" component={NotFoundPage}/>
      </Route>
    </Router>
  </Provider>
), document.getElementById('app'));
@gaearon
Copy link
Contributor

gaearon commented Apr 2, 2016

Closed in reduxjs/redux#1564 (comment).

@gaearon gaearon closed this as completed Apr 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants