Search code examples
reactjsreduxreact-reduxjsxredux-form

Redux action throwing "TypeError: Cannot read property 'num_of_players' of undefined" in mapStateToProps function


I'm new to react/redux and attempting to build a dice rolling game. I have mapStateToProps defined in my PregameContainer, as I'd like the user to be able to select the number of players and names of players then map it to the props of the children components for the duration of the game. However, when I attempt to alter the state in my store with game buttons, it throws the error:

error message

I understand that the mapStateToProps function will be called anytime the store data is altered. What I don't understand is why state is null the second time around.

Here's some of my files. Understand I'm EXTREMELY new to this framework, and I'm certain I don't have things in the right places. Any help would be greatly appreciated.

#PregameContainer.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PregameInput from './PregameInput'
import Game from '../Game'

class PregameContainer extends Component {
    

   render(){
    return (
        <div>
            {this.props.isSubmitted === false && <PregameInput submit={this.props.submit}/>}
            {this.props.isSubmitted && <Game settings={this.props}/>}
          
        </div>
        )
    }
}

const mapStateToProps = state => ({ 
    num_of_players: state.num_of_players, 
    p1: state.p1, 
    p2: state.p2, 
    p3: state.p3, 
    p4: state.p4, 
    isSubmitted: state.isSubmitted, 
     })

const mapDispatchToProps = dispatch => ({
  submit: game => dispatch({type: 'SUBMIT', game}),
  roll: roll => dispatch({type: 'ROLL', roll}),
  keep: keep => dispatch({type: 'KEEP', keep}),
  end: end => dispatch({type: 'END', end}),
})

export default connect(mapStateToProps, mapDispatchToProps)(PregameContainer)
#Game.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Score from './scoreboard/Score'
import PlayerKeep from './table/board/PlayerKeep'
import RollBoard from './table/board/RollBoard'
import ButtonsContainer from './table/board/ButtonsContainer'

class Game extends Component{
constructor(props) {
    super(props);
    this.state = {
        }
    }
    player(){ let x
        if (this.props.current_player === 1) x = this.props.settings.p1
        else if(this.props.current_player === 2) x = this.props.settings.p2
        else if(this.props.current_player === 3) x = this.props.settings.p3
        else x = this.props.settings.p4
        return x;
    }

    render()
    {
    return (
        <div className="game_container">
               <div>
            <div className="board_container">  
            <h4 > {this.player()}'s Turn!</h4>
            <h6 className="turn">TURN {this.props.current_turn} </h6>
            <br></br><br></br>
            <RollBoard settings={this.props}/>
            <br></br>
            <PlayerKeep settings={this.props}/>
            <ButtonsContainer settings={this.props}/>
        </div>
    </div>
                <Score settings={this.props.settings}/>
            </div>
    )
    }
}

    const mapStateToProps = state => ({p1_score: 0,
        p2_score: 0, 
        p3_score: 0, 
        p4_score: 0, 
        current_player: 1,
        current_turn: 1,
        keep_value: 0,
        rollable_dice: 6,
        rolled_dice: [],
        kept_dice: [],
        selected_value: 0,
    })


    

    
    export default connect(mapStateToProps)(Game) 
#manageGame.js (reducer)
export default function manageGame(state = {
    p1_score: 0,
    p2_score: 0, 
    p3_score: 0, 
    p4_score: 0, 
    current_player: 1,
    current_turn: 1,
    keep_value: 0,
    rollable_dice: 6,
    kept_dice: [],
    rolled_dice: [],
    selected_value: 0,
    p1:"Player 1",
    p2:"Player 2",
    p3:"Player 3",
    p4:"Player 4",
    num_of_players: 2,
    isSubmitted: false

    }, action) {

      switch (action.type) {

      case 'SUBMIT':
          const settings = { game: action.game };
          return {
            ...state,
            p1: settings.game.p1, 
            p2: settings.game.p2,
            p3: settings.game.p3,
            p4: settings.game.p4,
            num_of_players: settings.game.num_of_players,
            isSubmitted: true
            }

      case 'ROLL':
          return {
            ...state,
            rolled_dice: Array.from({length: state.rollable_dice}, () => Math.floor(Math.random() * 6))
            }, console.log(state);
            
           
  
        case 'KEEP':
          const keep = { game: action.keep };

          return{

        };

        case 'END':
          const end = { game: action.end };

          return{
            ...state,

          };

        default:
          return state;
    
      }
    }

This is what's throwing the error (the "roll" button)-

import React, { Component } from 'react';
import { connect } from 'react-redux'

class GameButtons extends Component {

    constructor(props) {
        super(props);
};    




render(){
    return (
        <div>
            {console.log(this.props)}
            <button onClick={this.props.settings.settings.roll} className="game_buttons" >Roll Dice</button>
            <button onClick={this.props.keep} className="game_buttons">Keep Selected</button> 
            <button onClick={this.props.end} className="game_buttons">End Turn</button>
        </div>
    )
}
}



export default connect()(GameButtons)

Solution

  • You have a bug in your reducer.

    case 'ROLL':
          return {
            ...state,
            rolled_dice: Array.from({length: state.rollable_dice}, () => Math.floor(Math.random() * 6))
            }, console.log(state);
            
    

    The return expression evaluates to undefined. The problem is that you unintentionally used javascript's little known comma operator. The return value will be the right hand value after the comma, the console.log() call, which is always undefined. That's why you get this error, since the redux state passed to mapStateToProps is now undefined.

    Get rid of the comma statement. If you want to write to the console, do it before the return statement.

     case 'ROLL':
          console.log(state);
          return {
            ...state,
            rolled_dice: Array.from({length: state.rollable_dice}, () => Math.floor(Math.random() * 6))
            };
    

    But it's even better to keep your reducer functions pure, and avoid any side effects inside your reducers (writing to the console is a side effect). You might consider using the redux-logger middleware instead https://www.npmjs.com/package/redux-logger