Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Larisa Popescu
Larisa Popescu
20,242 Points

Buttons don't work. I get an error saying that "Actions must be plain objects. Use custom middleware for async actions".

JavaScript > Building Applications with React and Redux > Putting it all Together > Updating the Player, Counter and AddPlayerForm Components

Larisa Popescu
Larisa Popescu
20,242 Points

The problem is that every time I press the + or - button from the app, I get this error: " createStore.js:165 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. at Object.dispatch [as updatePlayerScore] (createStore.js:165) at onClick (Counter.js?0f76:14) at HTMLUnknownElement.boundFunc (ReactErrorUtils.js:63) at Object.ReactErrorUtils.invokeGuardedCallback (ReactErrorUtils.js:69) at executeDispatch (EventPluginUtils.js:83) at Object.executeDispatchesInOrder (EventPluginUtils.js:106) at executeDispatchesAndRelease (EventPluginHub.js:41) at executeDispatchesAndReleaseTopLevel (EventPluginHub.js:52) at Array.forEach (<anonymous>) at forEachAccumulated (forEachAccumulated.js:22) "

This also happens when I want to remove a Player. But the add player button works just fine. I reviewed every video but I couldn't get this error.

Scoreboard.js

import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as PlayerActionCreators from '../actions/player';

import AddPlayerForm from '../components/AddPlayerForm';
import Header from '../components/Header';
import Player from '../components/Player';



class Scoreboard extends Component {

  static propTypes = {
    players: PropTypes.array.isRequired
  };

  render() {

    const {dispatch, players} = this.props;

    const addPlayer = bindActionCreators(PlayerActionCreators.addPlayer, dispatch);
    const removePlayer = (PlayerActionCreators.removePlayer, dispatch);
    const updatePlayerScore = (PlayerActionCreators.updatePlayerScore, dispatch);

    const playerComponents = players.map((player, index) => (
      <Player 
        index={index}
        name={player.name}
        score={player.score}
        key={player.name}
        updatePlayerScore={updatePlayerScore}
        removePlayer={removePlayer}
      />
    ));

    return (
      <div className="scoreboard">
        <Header players={players} />
        <div className="players">
          { playerComponents }
        </div>
        <AddPlayerForm addPlayer={addPlayer} />
      </div>
    );
  }
}

const mapStateToProps = state => (
  {
    players: state
  }
);


export default connect(mapStateToProps)(Scoreboard);
//Scoreboard is the container we want to connect to redux

actiontypes/player.js

export const ADD_PLAYER = 'player/ADD_PLAYER';
export const REMOVE_PLAYER = 'player/REMOVE_PLAYER';
export const UPDATE_PLAYER_SCORE = 'player/UPDATE_PLAYER_SCORE';

actions/player.js

import * as PlayerActionTypes from '../actiontypes/player';

export const addPlayer = name => {
    return {
        type: PlayerActionTypes.ADD_PLAYER,
        name
    };
};

export const removePlayer = index => {
    return {
        type: PlayerActionTypes.REMOVE_PLAYER,
        index
    };
};

export const updatePlayerScore = (index, score) => {
    return {
        type: PlayerActionTypes.UPDATE_PLAYER_SCORE,
        index, 
        score
    };
};

reducers/player.js

import * as PlayerActionTypes from '../actiontypes/player';

const initialState = [
    {
        name: 'Jim Hoskins',
        score: 31,
    },
     {
        name: 'Andrew Chalkley',
        score: 20,
    },
    {
        name: 'Alena Holligan',
        score: 50,
    },
];

export default function Player( state = initialState, action ) {
    switch(action.type) {
        case PlayerActionTypes.ADD_PLAYER:
        return [
            ...state,
            {
                name: action.name,
                score: 0
            }
        ];

        case PlayerActionTypes.REMOVE_PLAYER:
        return [
            ...state.slice(0, action.index),
            ...state.slice(action.index + 1)
        ];

        case PlayerActionTypes.UPDATE_PLAYER_SCORE:
        return state.map((player, index) => {
            if(index === action.index) {
                return {
                    ...player,
                    score: player.score + action.score
                };
            }
            return player;
        });

        default: 
            return state;
    }
}

Player.js

import React, { PropTypes } from 'react';

import Counter from './Counter';

const Player= props => {
    return (
      <div className="player">
        <div className="player-name">
          <a className="remove-player" onClick={ () => props.removePlayer(props.index)}>✖</a>
          {props.name}
        </div>
        <div className="player-score">
          <Counter 
            index={props.index} 
            updatePlayerScore={props.updatePlayerScore} 
            score={props.score} />
        </div>
      </div>
    );
}

Player.propTypes = {
    name: PropTypes.string.isRequired,
    index: PropTypes.number.isRequired,
    score: PropTypes.number.isRequired,
    removePlayer: PropTypes.func.isRequired,
    updatePlayerScore: PropTypes.func.isRequired
};

export default Player;

Counter.js

import React, { PropTypes } from 'react';

const Counter = props => {
    return (
      <div className="counter" >
        <button 
          className="counter-action decrement" 
          onClick={ () => props.updatePlayerScore(props.index, -1)}>
          -
        </button>
        <div className="counter-score"> {props.score} </div>
        <button 
          className="counter-action increment" 
          onClick={ () => props.updatePlayerScore(props.index, 1)}>
          +
        </button>
      </div>
    );
}

Counter.propTypes = {
    updatePlayerScore: PropTypes.func.isRequired,
    index: PropTypes.number.isRequired,
    score: PropTypes.number.isRequired,
};

export default Counter;

AddPlayerForm.js

import React, { Component, PropTypes } from 'react';

export default class AddPlayerForm extends Component {
    static propTypes: {
        addPlayer: PropTypes.func.isRequired,
    };

    state = {
        name: ""
    };

    onNameChange = e => {
        const name = e.target.value;
        this.setState({ name: name });
    };

    addPlayer = e => {
        if (e) e.preventDefault();
        this.props.addPlayer(this.state.name);
        this.setState({ name: '' });
    };

    render() {
        return (
          <div className="add-player-form">
            <form onSubmit={this.addPlayer}>
              <input
                type="text"
                value={this.state.name}
                onChange={this.onNameChange}
                placeholder="Player Name"
              />
              <input type="submit" value="Add Player" />
            </form>
          </div>
        );
    }
}

index.js

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import PlayerReducer from './src/reducers/player';
import Scoreboard from './src/containers/Scoreboard';   

const store = createStore(
    PlayerReducer
);

render (
    <Provider store={store}>
        <Scoreboard />
    </Provider>,
    document.getElementById('root')
);