Search code examples
javascriptreactjsreduxstatereducers

TypeError: Cannot read property 'type' of undefined Error before any Components even render


My Redux App won't even render the first component (App.js) as it keeps erroring. At some point, it's firing the reducer (shown below) with no action, so when the compiler reaches the line switch(action.type) it keeps thinking action is null and thus it cannot find the type property anywhere. In fact, I have a console.log as the very first line of a function that is called right away when App.js is rendered that does not even fire, which means the code breaks before it even renders a single component. Keep in mind I have the <Provider /> tags set up in index.js which renders App.js

------------------------REDUCER--------------------------

const rootReducer = combineReducers({
    battle: manageBattle,
    user: manageUser // Ignore this right now, it's not in any components that are getting rendered
  })
  export default rootReducer
function manageBattle(
    action,
    state={
        figures:{                       // The following figures are temporary as the stats will ne changed during the battle.
            user: {                     // We save the original user stats under the manageUser reducer
                is_user: true,                  // Might be needed. Might not, we'll find out
                name: "",                       // The name of the user figure
                class_type: null,
                id: null,                       // Generated in Rails
                hp: 0,                          // Health
                type: null,                     // Type, one of... TECH, WEAPONRY, SUPERNATURAL, ATHLETIC, NETHER, STEALTH. Each has a different effectiveness on the other
                level: 0,                       // Keeps track of how many fights they have won
                status: "none",                 // Certins moves (generated by Rails) have status effects. This variable holds what effects (if any) are present on the user
                spd: 0,                         // Speed
                atk: 0,                         // Attack
                def: 0,                         // Defense... you get it right?
                sAtk: 0,                        //
                sDef: 0,                        //
                tEffected: 0,                   // How many turns the user has been effected by a status effect. 0 if unaffected 
                image: "N/A",                   // Generated by Rails and used to determine with jpeg to load
                moves: [null]                   // Sets an empty array for the moves and setting that up is gonna suck so I'm avoiding it
            },
            opp: {
                is_user: false,
                name: "",
                class_type: null,
                id: null,                       // See Above
                hp: 0,
                type: null,
                level: 0,
                status: "none",
                spd: 0,
                atk: 0,
                def: 0,
                sAtk: 0,
                sDef: 0,
                tEffected: 0,
                image: "N/A",
                moves: [null]
            }
        },
        battle_details: {
            turns: 0,                           // This will keep track of the turns elapsed
            whoseTurn_id: null,                     // This will be set to 'user', 'opp', or 'none'. 
            this_move_target_id: null,
            prompt: "Welcome"
        }
    }
){
    if (!action){
        console.log("Action is nil")
    }
    switch (action.type) {
        case 'NEW_BATTLE':
        // FETCH NEW BATTLE JSON

        case 'CHANGE_USER_HP':
            // action = {type: "CHANGE_OPP_HP", amount: {Number generated in Event Listener inside of Moves}, effect: {String generated in Event Listener} }
            let effect = action.effect
            let newUserState = applyingStatusEffectandDamage(state, action.amount, effect)
            return {newUserState}

        case 'CHANGE_OPP_HP':
            // action = {type: "CHANGE_OPP_HP", amount: {Number generated in Event Listener inside of Moves}, effect: {String generated in Event Listener} }
            effect = action.effect
            let newOppState = applyingStatusEffectandDamage(state, action.amount, effect)
            return {newOppState}

        case 'COMPLETE_BATTLE':
            // CHANGE PROMPT, RESET USER HP, LEVEL UP
        
        default:
            return state;
    }
};

------------------App.js---------------------

import { connect } from 'react-redux';
import './App.css';
import BattleCard from './components/containers/BattleCard';
import React, { Component } from 'react';
import CreationContainer from './components/dispatchers/CreationContainer';

class App extends Component {

  intro_or_resume = (props) => {
    console.log(props)
    if (props.user.created === true){
      return(
        <div className="Battle Container">
           <BattleCard />
        </div>
      )
    }
    else{
      return(
        <div className="Create Form">
           <CreationContainer />
        </div>
      )
    }
  }
  testPlease = () => {
    console.log("hello!")
  }
  render() {
    return (
      <div className="Main-Window">
        {this.testPlease()}
        {this.intro_or_resume(this.props)}
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return { user: state.user }
}

export default connect(mapStateToProps)(App);

It's the this.testPlease() function that is not being hit, and I can tell that action = nil in the reducers when it's called because if (!action){console.log("Action is nil")} keeps getting hit every time I try to start the app. npm start is also flagging me for a few minute warnings, but nothing I think could be affecting this; no actual errors are being generated anywhere but the browser


Solution

  • Your reducer function needs order the state first and action after.

    const rootReducer = combineReducers({
      battle: manageBattle,
      user: manageUser // Ignore this right now, it's not in any components that are getting rendered
    })
    export default rootReducer
    function manageBattle(
      state={
          figures:{                       // The following figures are temporary as the stats will ne changed during the battle.
              user: {                     // We save the original user stats under the manageUser reducer
                  is_user: true,                  // Might be needed. Might not, we'll find out
                  name: "",                       // The name of the user figure
                  class_type: null,
                  id: null,                       // Generated in Rails
                  hp: 0,                          // Health
                  type: null,                     // Type, one of... TECH, WEAPONRY, SUPERNATURAL, ATHLETIC, NETHER, STEALTH. Each has a different effectiveness on the other
                  level: 0,                       // Keeps track of how many fights they have won
                  status: "none",                 // Certins moves (generated by Rails) have status effects. This variable holds what effects (if any) are present on the user
                  spd: 0,                         // Speed
                  atk: 0,                         // Attack
                  def: 0,                         // Defense... you get it right?
                  sAtk: 0,                        //
                  sDef: 0,                        //
                  tEffected: 0,                   // How many turns the user has been effected by a status effect. 0 if unaffected 
                  image: "N/A",                   // Generated by Rails and used to determine with jpeg to load
                  moves: [null]                   // Sets an empty array for the moves and setting that up is gonna suck so I'm avoiding it
              },
              opp: {
                  is_user: false,
                  name: "",
                  class_type: null,
                  id: null,                       // See Above
                  hp: 0,
                  type: null,
                  level: 0,
                  status: "none",
                  spd: 0,
                  atk: 0,
                  def: 0,
                  sAtk: 0,
                  sDef: 0,
                  tEffected: 0,
                  image: "N/A",
                  moves: [null]
              }
          },
          battle_details: {
              turns: 0,                           // This will keep track of the turns elapsed
              whoseTurn_id: null,                     // This will be set to 'user', 'opp', or 'none'. 
              this_move_target_id: null,
              prompt: "Welcome"
          }
      }, action
    ){
      if (!action){
          console.log("Action is nil")
      }
      switch (action.type) {
          case 'NEW_BATTLE':
          // FETCH NEW BATTLE JSON
    
          case 'CHANGE_USER_HP':
              // action = {type: "CHANGE_OPP_HP", amount: {Number generated in Event Listener inside of Moves}, effect: {String generated in Event Listener} }
              let effect = action.effect
              let newUserState = applyingStatusEffectandDamage(state, action.amount, effect)
              return {newUserState}
    
          case 'CHANGE_OPP_HP':
              // action = {type: "CHANGE_OPP_HP", amount: {Number generated in Event Listener inside of Moves}, effect: {String generated in Event Listener} }
              effect = action.effect
              let newOppState = applyingStatusEffectandDamage(state, action.amount, effect)
              return {newOppState}
    
          case 'COMPLETE_BATTLE':
              // CHANGE PROMPT, RESET USER HP, LEVEL UP
          
          default:
              return state;
      }
    };
    

    Hope this works for you.