実現したいこと、知りたいこと
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のひな型になるオブジェクト
JavaScript
1const initialData = { 2 3 // StoreCards 4 cards: { 5 6 // 'card-1': { id: 'card-1', content: 'Take out the garbage' }, 7 // 'card-2': { id: 'card-2', content: 'Wtatch my favorite show' }, 8 // 'card-3': { id: 'card-3', content: 'Change my phone' }, 9 // 'card-4': { id: 'card-4', content: 'Cook dinner' }, 10 }, 11 lists: { 12 13 // 'list-1': { 14 // id: 'list-1', 15 // title: 'add a list...', 16 // cardIds: ['card-1', 'card-2', 'card-3'], 17 // }, 18 19 // 'list-2': { 20 // id: 'list-2', 21 // title: 'add a list...2', 22 // cardIds: ['card-4'], 23 // }, 24 25 // 'list-3': { 26 // id: 'list-3', 27 // title: 'add a list...3', 28 // cardIds: ['card-5', 'card-6'], 29 // }, 30 }, 31 32 listOrder: [ 33 // 'list-1', 'list-2', 'list-3' 34 ], 35} 36 37export default initialData; 38
- reducer/index.js
CREATE_NEW_CARDでstateを更新したのちの話です。
JaVaScript
1// root reducer 2 3import * as actionTypes from '../../action_namespace'; 4import initialData from '../../initialData'; 5 6const initialState = { ...initialData }; 7 8const reducer = (state = initialState, action) => { 9 10 switch (action.type) { 11 case actionTypes.CREATE_NEW_CARD: 12 13 console.log('create new card'); 14 15 if (state.listOrder.includes(action.list)) { 16 17 const newCards = { ...state.cards }; 18 const amountOfCards = Object.keys(newCards).length; 19 newCards[`card-${amountOfCards + 1}`] = { 20 id: `card-${amountOfCards + 1}`, 21 content: action.content, 22 }; 23 24 const newLists = { ...state.lists }; 25 newLists[action.list].cardIds.push(`card-${amountOfCards + 1}`); 26 27 return { 28 ...state, 29 cards: newCards, 30 lists: newLists, 31 }; 32 33 } 34 else { 35 return state; 36 } 37 38 default: return state; 39 } 40} 41 42export 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" },
あなたの回答
tips
プレビュー