Skip to content
This repository has been archived by the owner on Oct 26, 2018. It is now read-only.

Getting 'Uncaught TypeError: Cannot read property 'type' of undefined' when using with react-router 2.x #182

Closed
trattles opened this issue Jan 11, 2016 · 26 comments

Comments

@trattles
Copy link

I am getting an error when trying to set up redux simple router.

I'm setting up redux simple router like so (excuse messy imports from various features). Where am I going wrong?

"redux-simple-router": "^2.0.1",
"react-router": "2.0.0-rc4",

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import {createStore, applyMiddleware, combineReducers} from 'redux';
import {Provider} from 'react-redux';
import reducers from './../common/Reducers/index.js';
import thunkMiddleware from 'redux-thunk';
import { syncHistory, routeReducer } from 'redux-simple-router';

const reducer = combineReducers(Object.assign({}, reducers, {routing: routeReducer }));
const initialState = window.__INITIAL_STATE__;
const simpleRouter = syncHistory(browserHistory);
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, simpleRouter)(createStore);

const store = createStoreWithMiddleware(reducer(initialState));
simpleRouter.syncHistoryToStore(store);

import routeConfig from './../common/routes/Routes.js';
import Radium, {Style, StyleRoot} from 'radium';
import Normalize from './../common/styles/Normalize.js';



ReactDOM.render(
    <Provider store={store}>
        <StyleRoot>
        <Router routes={routeConfig} history={browserHistory}>
        </Router>
        <Style rules={Normalize} />
        </StyleRoot>
    </Provider>
, document.getElementById('app-mount'));

This results in the following error:

Uncaught TypeError: Cannot read property 'type' of undefined

Here is the relevant source of the error - the "var type = _ref.type;"

// Reducer

    var initialState = {
      location: undefined
    };

    function routeReducer() {
      var state = arguments.length <= 0 || arguments[0] === undefined ? initialState : arguments[0];
      var _ref = arguments[1];
      var type = _ref.type;
      var location = _ref.location;

      if (type !== UPDATE_LOCATION) {
        return state;
      }

      return { location: location };
    }
@cbrwizard
Copy link

I am receiving this too. Must be related to the updated API of react router

@mjrussell
Copy link

@trattles is this on load?

Are you dispatching any actions in your app using the history API or routingActions?

This looks like you are dispatching an action that has no type field defined which is not a valid redux action. If you add the loggerMiddleware do you see a console log for an action with no type field?

@trattles
Copy link
Author

@mjrussell this error occurs on load. I am not dispatching any actions via the history API. Thus far all I've done is attempt to hook up redux-simple-router, but never got far enough to attempt to dispatch actions to it because I get this error on the initial render.

Before attempting to implement redux-simple-router, react-router was running just fine and the application rendered with no errors.

I'll add the logger middleware and see if it gives me anything useful.

@trattles
Copy link
Author

Looks like the error is related to this line

const store = createStoreWithMiddleware(reducer(initialState));

It must have to do with me setting the initialState incorrectly. The initialState passed from my server does not include the routing state. If I do not pass an initialState to my reducer it seems to run correctly.

Do I need to manually set a specific initial state when utilizing redux-simple-router if I want to pass other state parameters?

@mjrussell
Copy link

@trattles damn I thought that might be it when I saw you initalState.

Can you try setting your initial state of the routeReducer to this: https://github.com/rackt/redux-simple-router/blob/master/src/index.js#L32

@trattles
Copy link
Author

@mjrussell I tried setting it that way, but I've got other initial state items. So my initial state looks like this.

const initialState = {

                profile: {
                    name: 'Bob',
                    age: 10
                },
                location: undefined,
                messages: [],
                request:{
                    userAgent: req.headers['user-agent']
                },

            }

When I start with this, I get the same error.

My guess is the issue is here

 function routeReducer() {
      var state = arguments.length <= 0 || arguments[0] === undefined ? initialState : arguments[0];
      var _ref = arguments[1];
      var type = _ref.type;
      var location = _ref.location;

      if (type !== UPDATE_LOCATION) {
        return state;
      }

      return { location: location };
    }

var _ref = arguments[1]. Arguments[1] is undefined. Maybe I'm off though, but the line var type= _ref.type is what's throwing the error.

@mjrussell
Copy link

@trattles the initalState would be:

const initialState = {

                profile: {
                    name: 'Bob',
                    age: 10
                },
                routing: {
                   location: undefined,
                },
                messages: [],
                request:{
                    userAgent: req.headers['user-agent']
                },

            }

But regardless I see your problem now...

Change this line

const store = createStoreWithMiddleware(reducer(initialState));

to

const store = createStoreWithMiddleware(reducer, initialState);

I bet you don't even need the initial state for the routing now though

@jlongster
Copy link
Member

The second argument to the reducer should be an action. I don't know why that would not be defined. Can you break on that line and look at the stack trace, and see why it isn't being called with an action?

@trattles
Copy link
Author

I've gotten the issue resolved. The cause was how I was combining my reducers.

Originally when importing my reducers I was importing already combined reducers, then running that through combine reducers again to add redux-simple-router.

I refactored my reducers to instead return an object containing all my reducers, and then combine only once with redux-simple-router and everything seems to be working correctly.

I also do not need to manually add routing to state.

The final working entry.js looks like this

const reducer = combineReducers(Object.assign({}, reducers, {routing: routeReducer }));

let initialState = window.__INITIAL_STATE__;

const simpleRouter = syncHistory(browserHistory);

const createStoreWithMiddleware = applyMiddleware(thunkMiddleware, simpleRouter, logger)(createStore);

const store = createStoreWithMiddleware(reducer, initialState);

simpleRouter.syncHistoryToStore(store);

And my reducers no longer return combined reducers, but rather return an object like this (this is imported as 'reducers' above)

export default {messages, profile};

Having discovered this, I think it might be better for me to actually import redux simple router where I'm building my reducers, rather than in my entry.js, as that would eliminate my need to re-write all this on the server side.

@hoodsy
Copy link

hoodsy commented Jan 15, 2016

@trattles I have my reducers combining correctly, but still get this error.

Did you make any other changes that could have effected things?

@mjrussell
Copy link

@hoodsy the problem @trattles had was that he was invoking his reducer accidentally when creating the store causing a null action to be sent. My guess is you have something similar, but without source its impossible to tell

@hoodsy
Copy link

hoodsy commented Jan 15, 2016

@mjrussell my mistake - my error is actually:

Unhandled rejection TypeError: Cannot read property 'listen' of undefined
    at middleware (/Volumes/OSX Storage/Documents/projects/anchor/anchor/node_modules/redux-simple-router/lib/index.js:72:33)
    at /Volumes/OSX Storage/Documents/projects/anchor/anchor/node_modules/redux/lib/utils/applyMiddleware.js:50:16
    at Array.map (native)
    at /Volumes/OSX Storage/Documents/projects/anchor/anchor/node_modules/redux/lib/utils/applyMiddleware.js:49:27

My store config is a bit verbose, as I have a workaround to prevent redux-localstorage from being used server side:

// Init
// ====
let rootReducer
let storage
let createStoreWithMiddleware
export const reduxRouterMiddleware = syncHistory(browserHistory)
const middleware = [
  thunk,
  authenticationRouter,
  reduxRouterMiddleware
]

// Build createStoreWithMiddleware
// ===============================
if (typeof window !== 'undefined') {
  // Client store config (include localStorage)
  rootReducer = getPersistedState(anchorApp)
  storage = configClientStorage()
  createStoreWithMiddleware = compose(
    applyMiddleware(...middleware),
    persistState(storage, 'UID-1337'),
    devTools(),
  )(createStore)
} else {
  // Server store config
  rootReducer = anchorApp
  createStoreWithMiddleware = compose(
    applyMiddleware(...middleware),
    devTools(),
  )(createStore)
}

// Export store creator
// ====================
export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducer, initialState)
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers')
      store.replaceReducer(nextReducer)
    })
  }
  return store
}

anchorApp is my rootReducer, created by combineReducers.

client:

// Init Store
// ==========
const initialState = window.__INITIAL_STATE__
const store = configureStore(initialState)

// Init Router
// ===========
reduxRouterMiddleware.listenForReplays(store)

render(
  <div>
    <Provider store={ store }>
      <Router history={ browserHistory }>
        { routes }
      </Router>
    </Provider>
    <DebugPanel top right bottom>
      <DevTools store={ store } monitor={ LogMonitor } />
    </DebugPanel>
  </div>,
  document.getElementById('root')
)

hopefully this helps, sorry its a bit long!

@mjrussell
Copy link

@hoodsy can you show your import statement for the browserHistory in the init section?

@hoodsy
Copy link

hoodsy commented Jan 15, 2016

@mjrussell here it is:

import { compose, createStore, applyMiddleware } from 'redux'
import { devTools } from 'redux-devtools'
import thunk from 'redux-thunk'
import { syncHistory } from 'redux-simple-router'
import { browserHistory } from 'react-router'

@mjrussell
Copy link

Hmm your case really does look like the history object passed to syncHistory is undefined there. Can you console.log the browserHistory and ensure that it does have a listen method? You are boming right here https://github.com/rackt/redux-simple-router/blob/master/src/index.js#L51

@hoodsy
Copy link

hoodsy commented Jan 15, 2016

So it seems browserHistory is actually coming up as undefined on server and client. React Router seems to be installed properly...

I'm on "react-router": "^2.0.0-rc5" and "redux-simple-router": "^2.0.2"

@mjrussell
Copy link

Hmm I dont think any of your issues are actually redux-simple-router problems and nothing is jumping out at me. Might be worth following up with react-router stack overflow or discord (https://stackoverflow.com/questions/ask?tags=react-router) I think its beyond the scope of the original issue as well.

@hoodsy
Copy link

hoodsy commented Jan 15, 2016

Will do, thanks for the help.

@petermikitsh
Copy link

Same issue here -- browserHistory is undefined.

@hoodsy
Copy link

hoodsy commented Jan 16, 2016

@timdorr timdorr closed this as completed Jan 17, 2016
@petermikitsh
Copy link

@hoodsy Were you able to find a fix for this issue? Looked through the stack overflow conversation, didn't see one. Thanks.

@petermikitsh
Copy link

@wayspurrchen
Copy link

For anyone looking at this in the future, remix-run/react-router#2090 is the relevant issue for browserHistory from react-router being undefined, which is because you can't use react-router's browserHistory on the server side. Instead, you should use a pattern like that shown here: https://github.com/rackt/react-router/blob/master/docs/guides/advanced/ServerRendering.md

@rpedroni
Copy link

rpedroni commented Feb 1, 2016

The problem is due to a change on the router's API: https://github.com/rackt/react-router/blob/1.0.x/docs/guides/basics/Histories.md

Use this instead
import createBrowserHistory from 'history/lib/createBrowserHistory';
const history = createBrowserHistory()

@antoniopresto
Copy link

See: https://github.com/rackt/react-router/blob/master/upgrade-guides/v2.0.0.md#using-custom-histories

// v1.x
import createHashHistory from 'history/lib/createHashHistory'
const history = createHashHistory({ queryKey: false })
<Router history={history}/>

// v2.0.0
import { Router, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
// useRouterHistory creates a composable higher-order function
const appHistory = useRouterHistory(createHashHistory)({ queryKey: false })
<Router history={appHistory}/>

I'm using this in server-side:

import {Router, RouterContext, match, useRouterHistory} from 'react-router';
import {createMemoryHistory} from 'history';

// ...
const appHistory = useRouterHistory(createMemoryHistory)({});
const component = (
  <Provider store={store} key="provider">
    <Router routes={routes} history={appHistory} />
  </Provider>
);

@walkerrandolphsmith
Copy link

@wayspurrchen on the link you provided it states:

For data loading, you can use the renderProps argument to build whatever convention you want--like adding static load methods to your route components, or putting data loading functions on the routes--it's up to you.

How is hydration of initial state handled on the server?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests