質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

react-redux-router,ConnectedRouterでエラー、"TypeError: Cannot read property 'dispatch' of undefined"

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,688

increment-P

score 7

長文すみません。
react-redux-routerを勉強しているのですが、どのサンプルを入力しても同様のエラーが出て何か基本的な所で躓いているようなのですが解決できずに困っています。
ご指導いただけないでしょうか?

  • WSL ubuntu 18.04
  • yarn 1.13.0
  • npm 6.4.1

yarn add redux react-redux redux-thunk redux-logger react-router-dom react-router-redux@next history --dev
↓ インストール直後のpackage.jsonです

{
  "name": "my-project",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-scripts": "2.1.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
  "devDependencies": {
    "history": "^4.7.2",
    "react-redux": "^6.0.0",
    "react-router-dom": "^4.3.1",
    "react-router-redux": "^5.0.0-alpha.9",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0"
  }
}


この後、fetch-jsonpとqsをインストールしています。

翔泳社のReact入門のサンプルを入力するとエラーとなって実行できません。

以下import等端折ったソースです。

↓ src/index.js 

const history = createBrowserHistory();
const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);


↓ src/components/Nav.js 及び src/components/Ranking.js 

//Nav.js
export default function Nav({ categories }) {
  const to = category => (
    category.id === '1' ? '/all' : `/category/${category.id}`
  );

  return (
    <ul>
      {categories.map(category => (
        <li key={`nav-item-${category.id}`}>
          <Link to={to(category)}>
            {category.name}
          </Link>
        </li>
      ))}
    </ul>
  );
}
Nav.propTypes = {
  categories: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired
    })
  ).isRequired
};


//Ranking.js
export default class Ranking extends React.Component {
  componentWillMount() {
    this.props.onMount(this.props.categoryId);
  }
  componentWillReceiveProps(nextProps) {
    if (this.props.categoryId !== nextProps.categoryId) {
      this.props.onUpdate(nextProps.categoryId);
    }
  }

  render() {
    const { category, ranking, error } = this.props;

    return (
      <div>
        <h2>{
          typeof category !== 'undefined' ? `${category.name}のランキング` : ''
        }</h2>

        {(() => {
          if (error) {
            return <p>エラーが発生しました。リロードしてください。</p>;
          } else if (typeof ranking === 'undefined') {
            return <p>読み込み中...</p>;
          } else {
            return (
              <ol>
                {ranking.map(item => (
                  <li key={`ranking-item-${item.code}`}>
                    <img alt={item.name} src={item.imageUrl} />
                    <a href={item.url} target="_blank">{item.name}</a>
                  </li>
                ))}
              </ol>
            );
          }
        })()}
      </div>
    );
  }
}
Ranking.propTypes = {
  categoryId: PropTypes.string.isRequired,
  onMount: PropTypes.func.isRequired,
  onUpdate: PropTypes.func.isRequired,

  category: PropTypes.shape({
    id: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
  }),
  ranking: PropTypes.arrayOf(
    PropTypes.shape({
      code: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      url: PropTypes.string.isRequired,
      imageUrl: PropTypes.string.isRequired,
    })
  ),
  error: PropTypes.bool.isRequired
};
Ranking.defaultProps = {
    categoryId: '1'
};


↓ src/containers/Nav.js 及び src/containers/Ranking.js 

//Nav.js
const mapStateToProps = state => ({
  categories: state.shopping.categories
});
export default connect(mapStateToProps)(Nav);


//Ranking.js
const mapStateToProps = (state, ownProps) => ({
  categoryId: ownProps.categoryId,

  category: state.Ranking.category,
  ranking: state.Ranking.ranking,
  error: state.Ranking.error
});

const mapDispatchToProps = dispatch => ({
  onMount (categoryId) {
    dispatch(actions.fetchRanking(categoryId));
  },
  onUpdate (categoryId) {
    dispatch(actions.fetchRanking(categoryId));
  }
});
export default connect(mapStateToProps, mapDispatchToProps)(Ranking);

↓ src/reducers/index.js、src/reducers/Ranking.js 及び src/reducers/shopping.js  

//index.js
export { default as shopping } from './shopping';
export { default as Ranking } from './Ranking';

//Ranking.js
const getRanking = response => {
  const ranking = [];
  const itemLength = response.ResultSet.totalResultsReturned
  for (let index = 0; index < itemLength; index++) {
    const item = response.ResultSet['0'].Result[index + ''];
    ranking.push({
      code: item.Code,
      name: item.Name,
      url: item.Url,
      imageUrl: item.Image.Medium
    })
  }
  return ranking;
};

const initialState = {
  category: undefined,
  ranking: undefined,
  error: false
};

export default (state = initialState, action) => {
  switch (action.type) {
    case 'START_REQUEST':
      return {
        category: action.payload.category,
        ranking: undefined,
        error: false
      };

    case 'RECEIVE_DATA':
      return action.payload.error
        ? { ...state, error: true }
        : { ...state, ranking: getRanking(action.payload.response) };

    default:
      return state;
  }
}

//shopping.js
const initialState = {
  categories: [
    { id: '1', name: 'すべてのカテゴリ' },
    { id: '2502', name: 'パソコン、周辺機器' },
    { id: '10002', name: '本、雑誌、コミック' }
  ]
};
export default () => initialState;


↓ src/actions/Ranking.js 

const API_URL = 'yahooのAPIリクエストURL';
const APP_ID = '自分がyahooで取得したID';

const startRequest = category => ({
  type: 'START_REQUEST',
  payload: { category },
});
const receiveData = (category, error, response) => ({
  type: 'RECEIVE_DATA',
  payload: { category, error, response },
});
const finishRequest = category => ({
  type: 'FINISH_REQUEST',
  payload: { category },
});

// ランキングを取得する
export const fetchRanking = categoryId => {
  return async (dispatch, getState) => {
    const categories = getState().shopping.categories;
    const category = categories.find(category => (category.id === categoryId));
    if (typeof category === 'undefined') {
      dispatch(replace('/'));
      return;
    }
    dispatch(startRequest(category));

    const queryString = qs.stringify({
      appid: APP_ID,
      category_id: categoryId,
    });
    try {
      const responce = await fetchJsonp(`${API_URL}?${queryString}`);
      const data = await responce.json();
      dispatch(receiveData(category, null, data));
    } catch (err) {
      dispatch(receiveData(category, err));
    }
    dispatch(finishRequest(category));
  };
};


 ↓ src/createStore.js 

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      ...reducers,
      router: routerReducer,
    }),
    applyMiddleware(
      logger,
      thunk,
      routerMiddleware(history)
    )
  );
}

 ↓ src/fetchRanking.js 

const API_URL = 'yahooのAPIリクエストURL';
const APP_ID = '自分がyahooで取得したID';

export default function fetchRanking(categoryId) {
  const queryString = qs({
    appid: APP_ID,
    category_id: categoryId
  });
  return fetchJsonp(`${API_URL}?${qs}`);
}

 ↓ src/App.js 

class App extends Component {
  render() {
    return (
      <div className="App">
        <Nav />
        <Switch>
          <Route path="/all" component={Ranking} />
          <Route
            path="/category/1"
            render={() => <Redirect to="/all" />}
          />
          <Route
            path="/category/:id"
            render={
              ({ match }) => <Ranking categoryId={match.params.id} />
            }
          />
        </Switch>
      </div>
    );
  }
}
export default App;

 ↓ src/index.js[エントリーポイント] 

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import createBrowserHistory from 'history/createBrowserHistory';
import App from './App';
import createStore from './createStore';

const history = createBrowserHistory();
const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

実行結果は
 TypeError: Cannot read property 'dispatch' of undefined 
となります。

プラウザコンソールのデバッガで追いかけたりしたのですが自己解決できそうにありません。
よろしくお願いします・

このサイトのサンプルでも同様のエラーが出ます。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jun68ykt

    2019/01/25 11:38

    こちらのご質問、解決されたようでしたら解決済みにして頂けますと幸いです。さらなる疑問があれば、コメント頂ければと思います。

    キャンセル

回答 1

checkベストアンサー

+2

こんにちは

ご質問にある、 yarn add コマンド

yarn add redux react-redux redux-thunk redux-logger react-router-dom react-router-redux@next history --dev

が間違っているようです。具体的には最後の --dev が不要なので、いったん

yarn remove redux react-redux redux-thunk redux-logger react-router-dom react-router-redux@next history


でremoveしてから、あらためて --dev なしの

yarn add redux react-redux redux-thunk redux-logger react-router-dom react-router-redux@next history

で yarn addしてから動作確認してみるとよいと思います。

ちなみにご質問に挙げているソースコードは、同書籍サンプルコードの以下のフォルダ

Ch10_sample/Ch10_sample/5-reducer

にあるものと思います (間違っていたらご指摘ください) が、ダウンロードしたサンプルコードに含まれる 

  • Ch10_sample/Ch10_sample/5-reducer/package.json

は以下のようになっています。

{
  "name": "1-add-redux",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "fetch-jsonp": "^1.1.3",
    "history": "^4.7.2",
    "prop-types": "^15.6.0",
    "qs": "^6.5.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-redux": "^5.0.6",
    "react-router-dom": "^4.2.2",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-scripts": "1.1.0",
    "redux": "^3.7.2",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

したがって、書籍の説明に沿うならば、ご質問にある package.json の "devDependencies"  に追加されている以下のモジュール

  "devDependencies": {
    "history": "^4.7.2",
    "react-redux": "^6.0.0",
    "react-router-dom": "^4.3.1",
    "react-router-redux": "^5.0.0-alpha.9",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0"
  }

は、 "dependencies" のほうに追加されるべきものです。ですので、 yarn add時に --dev が不要と思われました。

参考になれば幸いです。

追記

react router が 4.2 から 4.3 になった際に、react-router-redux のコミッタである @timdorrさんが以下のようにアナウンスしています。

https://twitter.com/timdorr/status/1004390317179785216

React Router 4.3.0 is out!
  And I'm finally officially deprecating react-router-redux

ですので、問題の原因は react router 4.3 と react-router-reduxを組み合わせて使おうとしたことによるものと思われます。ご自身で yarn add で各モジュールをインストールせずに、 書籍のサンプルにある package.json をそのまま使って、以下のように yarn install コマンドで各モジュールをインストールするほうがよいです。

[5-reducer]$ pwd
/Users/jun68ykt/WebstormProjects/react-book/Ch10_sample/Ch10_sample/5-reducer
[5-reducer]$ cat package.json 
{
  "name": "1-add-redux",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "fetch-jsonp": "^1.1.3",
    "history": "^4.7.2",
    "prop-types": "^15.6.0",
    "qs": "^6.5.1",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-redux": "^5.0.6",
    "react-router-dom": "^4.2.2",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-scripts": "1.1.0",
    "redux": "^3.7.2",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
[5-reducer]$ yarn install
yarn install v1.12.3
[1/4] 🔍  Resolving packages...

(・・・中略)

success Saved lockfile.
✨  Done in 22.35s.

[5-reducer]$ yarn start


上記の手順で、意図通りの画面がブラウザに表示されると思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/01/24 10:07

    回答ありがとうございます。
    dependenciesとdevDependenciesの違いが理解できていませんでした。
    https://qiita.com/chihiro/items/ca1529f9b3d016af53ec

    ご指摘の通りyarnにてモジュールをインストールする際に
    "--dev"オプションを外してインストールしました。

    "yarn add redux react-redux redux-thunk redux-logger react-router-dom react-router-redux@next history fetch-jsonp qs"

    結果package.jsonは以下のようになりました。

    {
    "name": "my-project2",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
    "fetch-jsonp": "^1.1.3",
    "history": "^4.7.2",
    "qs": "^6.6.0",
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-redux": "^6.0.0",
    "react-router-dom": "^4.3.1",
    "react-router-redux": "^5.0.0-alpha.9",
    "react-scripts": "2.1.3",
    "redux": "^4.0.1",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0"
    },
    "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
    },
    "eslintConfig": {
    "extends": "react-app"
    },
    "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
    ]
    }

    ですが、エラー内容は変わらず・・・。

    "TypeError: Cannot read property 'dispatch' of undefined"

    ConnectedRouter._this.handleLocationChange
    node_modules/react-router-redux/es/ConnectedRouter.js:49

    return _ret = (_temp = (_this = _possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))), _this), _this.handleLocationChange = function (location) {
    _this.store.dispatch({ //←ここですと、、、
    type: LOCATION_CHANGE,
    payload: location
    });


    試しにyarn add の"--exact"オプションを使ってサンプル側のモジュールのバージョンを指定してインストールしてみました。

    "yarn add react@16.2.0 react-dom@16.2.0 react-redux@5.0.6 react-scripts@1.1.0 --exact prop-types@15.6.0 redux@3.7.2 react-redux@5.0.6 redux-logger@3.0.6 redux-thunk@2.2.0 react-router-dom@4.2.2 react-router-redux@5.0.0-alpha.9 history@4.7.2 fetch-jsonp@1.1.3 qs@6.5.1 --exact"
    こちらではきちんと動く事を確認しました。


    試しに、
    "yarn add redux react-redux redux-thunk redux-logger react-router-dom@next react-router-redux@next history fetch-jsonp qs"
    "react-router-dom"をnextでインストールしてみると、、、
    同じエラーで変わらず。

    とっかかりすら掴めず途方にくれています・・・。

    キャンセル

  • 2019/01/24 17:13

    @increment-Pさん

    回答に追記しましたので、試してみてください。

    キャンセル

  • 2019/01/24 22:12

    理解できました。ありがとうございました。

    キャンセル

  • 2019/01/25 11:35

    解決されたようですね、よかったです👍

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る