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

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

ただいまの
回答率

88.90%

react-redux ネスト下位のcontainerが持つmapStateToPropsが更新されない

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 158

tiiseto

score 0

実現したいこと、知りたいこと

react-redux, react-beautiful-dndを利用してtrelloクローンを作成している初心者です。

現在、以下のようなネスト構成で開発し始めています  

App.js
    └─ MainContent.js
        ├─ Lists.js              // container
        │    └─ List.js         // presentation
        │       └ Cards.js      // container
        │           └ Card.js   // presentation
        │
        │
        └─ AddLists.js          // container

  
containerはreact-reduxのconnect()で接続されたコンポーネントという意味で話します。  

例えば、  
store.stateが更新されて、  
Lists.js、Cards.jsがconnect()で接続されており、それらのmapStateToProps()がオブジェクトを返すとします。  
<br>
その時、  
両者のmapStateToProps()がまったく同じオブジェクト内容を返すとしても、  
ネストの上位のファイルのmapStateToProps()しか実行されず、  
同じ内容を取得するはずの下位のファイルのmapStateToProps()は実行されないので  
下位のファイルでは更新されたstore.stateが取得できずに  
困っています。  

私のreact-reduxへの理解は、store.stateが更新されれば必ずすべてのmapStateToProps()が実行するものと思っていました。  
しかしどうもこの理解を修正しなくてはいけないようです。  

そこで知りたいのが、  
「react-reduxは、connect()するコンポーネントをネストの一番上だけに限定して、下位の途中に追加してはいけない仕様なのでしょうか?」ということです。  

すこしネットで探してみると、ネストの途中でconnect()しているコンポーネントを挟んでいるようなコードは見かけます。  
react-reduxの公式を見てみても、     
store.stateが更新されたらすべての接続されたmapStateToProps()は実行されるとあります。  

この公式の文言は、別々のネスト同士の場合という前提で理解すべきでしょうか?
それとも別のところに問題があるのでしょうか?  
やり方の問題ではあるのでしょうが、どこを修正すればいいのか見当がつきません。  

どうかこのことについて詳しい方は、教えてくださいませんでしょうか。  

エラー内容など

エラー内容:

TypeError: Cannot read property 'content' of undefined


エラー発生個所 : Cards.js  

エラー発生理由 : Cards.jsのmapStateToProps()でstate.cardsが更新されず空オブジェクトのままで、その中身を読み取ろうとしたから。  

const mapStateToProps = state => {
    return {
        cards: state.cards,         // 更新されなかった
    }
} 

ソースコード

  • store.stateのひな型になるオブジェクト
const initialData = {

    // StoreCards
    cards: {

        // 'card-1': { id: 'card-1', content: 'Take out the garbage' },
        // 'card-2': { id: 'card-2', content: 'Wtatch my favorite show' },
        // 'card-3': { id: 'card-3', content: 'Change my phone' },
        // 'card-4': { id: 'card-4', content: 'Cook dinner' },
    },
    lists: {

        // 'list-1': {
        //     id: 'list-1',
        //     title: 'add a list...',
        //     cardIds: ['card-1', 'card-2', 'card-3'],
        // },

        // 'list-2': {
        //     id: 'list-2',
        //     title: 'add a list...2',
        //     cardIds: ['card-4'],
        // },

        // 'list-3': {
        //     id: 'list-3',
        //     title: 'add a list...3',
        //     cardIds: ['card-5', 'card-6'],
        // },
    },

    listOrder: [
        // 'list-1', 'list-2', 'list-3'
    ],
}

export default initialData;

  

  • reducer/index.js  
    CREATE_NEW_CARDでstateを更新したのちの話です。  
// root reducer

import * as actionTypes from '../../action_namespace';
import initialData from '../../initialData';

const initialState = { ...initialData };

const reducer = (state = initialState, action) => {

    switch (action.type) {
        case actionTypes.CREATE_NEW_CARD:

            console.log('create new card');

            if (state.listOrder.includes(action.list)) {

                const newCards = { ...state.cards };
                const amountOfCards = Object.keys(newCards).length;
                newCards[`card-${amountOfCards + 1}`] = {
                    id: `card-${amountOfCards + 1}`,
                    content: action.content,
                };

                const newLists = { ...state.lists };
                newLists[action.list].cardIds.push(`card-${amountOfCards + 1}`);

                return {
                    ...state,
                    cards: newCards,
                    lists: newLists,
                };

            }
            else {
                return state;
            }

        default: return state;
    }
}

export default reducer;
  • App.js
import React from 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import { connect } from 'react-redux';

import MainContent from './MainContent';
import classes from './App.module.css';
import { cardMoveToSameList } from '../store/action';
import * as actionTypes from '../action_namespace';

class App extends React.Component {

    onDragEnd = result => {}

    render() {
        return (
            <DragDropContext onDragEnd={this.onDragEnd}>
                <div className={classes.App}>
                    <MainContent />
                </div>
            </DragDropContext>
        );
    }
}

export default App;

  

  • Lists.js
import React from 'react';
import { connect } from 'react-redux';

import classes from './Lists.module.css';
import List from './List';

class Lists extends React.Component {

    render() {
        console.log(this.props);

        let lists = this.props.listOrder.map(list => {
            return (
                <List
                    className={classes.Lists}
                    key={this.props.lists[list].id}
                    id={this.props.lists[list].id}
                    title={this.props.lists[list].title}
                    listName={list}
                    cards={this.props.cards}
                />
            );
        });

        return (
            <div className={classes.Lists} >
                {lists}
            </div>
        );
    }
}


// 更新される
const mapStateToProps = state => {

    return {
        cards: state.cards,
        lists: state.lists,
        listOrder: state.listOrder
    }
}

export default connect(mapStateToProps)(Lists);

  

  • List.js  
import React from 'react';
import Cards from './Cards';
import classes from './List.module.css';

const list = props => {
    return (
        <div
            id={props.id} className={classes.List} >
            <span>{props.title}</span>
            <Cards listName={props.listName} cards={props.cards} />
        </div>
    );
}

export default list;

  

  • Cards.js
import React from 'react';
import { connect } from 'react-redux';
import { Droppable } from 'react-beautiful-dnd';

import DroppableContainer from '../dnd/droppableContainer/droppableContainer';
import classes from './Cards.module.css';
import Card from './Card';
import { createNewCard } from '../store/action';


// @props : List.jsからprops.listNameを引き継ぐ
class Cards extends React.Component {

    state = { inputValue: '' }

    keyUpHandler = (event) => {
        const ENTER_KEY = 13;
        if (event.keyCode === ENTER_KEY
            && this.state.inputValue !== '') {
            this.props.onCreateNewCard(this.props.listName, this.state.inputValue);
            this.setState({ inputValue: '' });
        }
    }

    addCardHandler = () => {
        if (this.state.inputValue !== '') {
            this.props.onCreateNewCard(this.props.listName, this.state.inputValue);
            this.setState({ inputValue: '' });
        }
    }

    onChangeHandler = (event) => {
        this.setState({ inputValue: event.target.value });
    }

    render() {

        // this.props.lists[].cardIdsには要素があるのに、
        // this.props.cardsが空オブジェクトなので、
        // エラーが起こる
        const cardsComp = this.props.lists[this.props.listName].cardIds.map((cardId, index) => {

        return (
            <Card
                key={cardId}
                id={cardId}
                content={this.props.cards[cardId].content}      // エラー個所
                index={index}
            />
        )
    });

        return (
            <div className={classes.Cards}>
                <Droppable droppableId={this.props.listName}>
                    {(provided, snapshot) => (
                        <DroppableContainer
                            innerRef={provided.innerRef}
                            {...provided.droppableProps}
                            provided={provided}
                        >
                            {cardsComp}
                            {provided.placeholder}
                        </DroppableContainer>
                    )}
                </Droppable>
            </div>
        );
    }
}


// store.stateが更新されたのに更新されない
const mapStateToProps = state => {

    return {
        cards: state.cards,     // state.cardsが更新されない
        lists: state.lists,
        listOrder: state.listOrder,
    }
}

const mapDispatchToProps = dispatch => {
    return {
        onCreateNewCard: (list, card) => dispatch(createNewCard(list, card)),
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Cards);

やってみたこと

  • ネストの上位のmapStateToProp()しか実行されないのはLists.jsやCards.jsに限定された事象なのか?  

App.jsをconnect()してmapStateToProps()を定義したら、App.jsのmapStateToProps()が実行されて、Lists.jsのほうは実行されなかったので、限定された事象ではなかった。  

  • store.stateは正しく更新されているのか?  

loggerを定義して更新前後のstateをコンソールに出力させましたが、正しく更新されているのは確認できました。  

  • 「mapStateToProps()が実行されない」の定義は?  

mapStateToProps()内にconsole.log()を設けました。コンソールに出力されなかった、且つ実際にmapStateToProps()が更新されたstore.stateを受け取っていなかったとき実行されなかったと判断しています。  

補足情報

package.json  

{
  "dependencies": {
    "react": "^16.13.1",
    "react-beautiful-dnd": "^13.0.0",
    "react-dom": "^16.13.1",
    "react-redux": "^7.2.0",
    "react-scripts": "3.4.1",
    "redux": "^4.0.5"
  },
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正の依頼

  • hoshi-takanori

    2020/07/12 10:27 編集

    react-beautiful-dnd は内部で redux を使ってるので、それと衝突してるのかも。解決方法は分かりません。

    キャンセル

まだ回答がついていません

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

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

関連した質問

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