Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

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

Why is my Scoreboard app not displaying?

I followed Guil meticulously through each and every step of this course. Only for me to get this error in my chrome browser console as the Scoreboard didn't display.

warning.js:33 Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in.

invariant.js:42 Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in. at invariant (invariant.js:42) at ReactCompositeComponentWrapper.instantiateReactComponent [as _instantiateReactComponent] (instantiateReactComponent.js:72) at ReactCompositeComponentWrapper.performInitialMount (ReactCompositeComponent.js:364) at ReactCompositeComponentWrapper.mountComponent (ReactCompositeComponent.js:255) at Object.mountComponent (ReactReconciler.js:43) at mountComponentIntoNode (ReactMount.js:102) at ReactReconcileTransaction.perform (Transaction.js:141) at batchedMountComponentIntoNode (ReactMount.js:124) at ReactDefaultBatchingStrategyTransaction.perform (Transaction.js:141) at Object.batchedUpdates (ReactDefaultBatchingStrategy.js:60

I went back to look through the videos to compare his code and mine if there's any difference, I found few but the app still didn't display in the browser.

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/container/Scoreboard';

const store = createStore(
    PlayerReducer,
    window.devToolsExtension && window.devToolsExtension()
);

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

Scoreboard.js

// Container component

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

// Container component
class Scoreboard extends Component {

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

  render() {
    const { dispatch, players } = this.props;

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

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

    });

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

}

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

export default connect(mapStateToProps)(Scoreboard);

player.js...\actiontypes

// Actions expressed as string constants

import React from 'react';

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

player.js...\reducers

// Reducers

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.CHANGE_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...\actions

//  Action creator

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 changePlayerScore = (index, score) => {
    return {
        type: PlayerActionTypes.CHANGE_PLAYER_SCORE,
        index,
        score
    }
}

AddPlayerForm.js

// Logical component

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>
        );
    }

}

Player.js

// Pure component with component dependency

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}
                    changePlayerScore={props.changePlayerScore}
                    score={props.score} />
            </div>
        </div>
    )
}

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

export default Player;

Counter.js

// Pure component

import React, { PropTypes } from 'react';

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

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

export default Counter;

Header.js

// Pure component with component dependency

import React, { PropTypes } from 'react';
import Stats from './Stats';
import Stopwatch from './Stopwatch';

const Header = (props) => {
    return (
        <div className="header">
            <Stats players={props.players} />
            <h1>Scoreboard</h1>
            <Stopwatch />
        </div>
    );
}

Header.propTypes = {
    players: PropTypes.array.isRequired,
};

export default Header;

Stopwatch.js

// Logical component

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

export default class Stopwatch extends Component {

    state = {
        running: false,
        previouseTime: 0,
        elapsedTime: 0
    };

    componentDidMount() {
        this.interval = setInterval(this.onTick);
    }

    componentWillUnmount() {
        clearInterval(this.interval);
    }

    onStart = () =>
        this.setState({
            running: true,
            previousTime: Date.now(),
        });

    onStop = () =>
        this.setState({
            running: false,
        });

    onReset = () =>
        this.setState({
            elapsedTime: 0,
            previousTime: Date.now(),
        });

    onTick = () => {
        if (this.state.running) {
            var now = Date.now();
            this.setState({
                elapsedTime: this.state.elapsedTime + (now - this.state.previousTime),
                previousTime: Date.now(),
            });
        }
    }

    render = () => {
        var seconds = Math.floor(this.state.elapsedTime / 1000);
        return (
            <div className="stopwatch" >
                <h2>Stopwatch</h2>
                <div className="stopwatch-time"> {seconds} </div>
                {this.state.running ?
                    <button onClick={this.onStop}>Stop</button>
                    :
                    <button onClick={this.onStart}>Start</button>
                }
                <button onClick={this.onReset}>Reset</button>
            </div>
        )
    }

}

Stats.js

// Pure component

import React, { PropTypes } from 'react';

const Stats = (props) => {
    const playerCount = props.players.length;
    const totalPoints = props.players.reduce(function (total, player) {
        return total + player.score;
    }, 0);

    return (
        <table className="stats">
            <tbody>
                <tr>
                    <td>Players:</td>
                    <td>{playerCount}</td>
                </tr>
                <tr>
                    <td>Total Points:</td>
                    <td>{totalPoints}</td>
                </tr>
            </tbody>
        </table>
    )
}

Stats.propTypes = {
    players: PropTypes.array.isRequired
}

export default Stats;
Calin Bogdan
Calin Bogdan
14,921 Points

Hello!

Could you please show us the code you're currently struggling with? It's easier to find the error this way.

Thanks!

Done :)

1 Answer

Calin Bogdan
Calin Bogdan
14,921 Points

My guess is that Guil recorded the tutorial on React 15 at most and your version of React is v16.

You can find this on the React website:

React.PropTypes has moved into a different package since React v15.5. Please use the prop-types library instead.

Later edit after I figured out I forgot to explain: You can see at the top of each file that you're importing React and PropTypes from the react package.

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

After you run npm install --save prop-types you need to change that into:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

Hope this helped!

How can I use the prop-types library?

Calin Bogdan
Calin Bogdan
14,921 Points

Run

npm install --save prop-types

in the console / terminal inside the project folder

Thank you very much Calin for all the assistance but unfortunately nothing changed. I'm still getting the same error in the browser console

Calin Bogdan
Calin Bogdan
14,921 Points

In index.js you're importing Scoreboard component from the container folder

import Scoreboard from './src/container/Scoreboard';

Any chance you might have mistyped containers?

Just cross-checked, no misspelling errors with the container directory.

Calin Bogdan
Calin Bogdan
14,921 Points

I found a missing semicolon in Scoreboard.js

import * as PlayerActionCreators from '../actions/player'  <---

Lol, I didn't even notice that. You have an Eagle's eye Calin. The Scoreboard is still not displaying. I really don't know what's wrong with this code TBH.

Calin Bogdan
Calin Bogdan
14,921 Points

In index.js,

import Provider from 'react-redux';

should be

import { Provider } from 'react-redux';

Nice one Calin, we're getting somewhere. The error in the browser console has changed. These are the new errors:

mapStateToProps() in Connect(Scoreboard) must return a plain object. Instead received undefined. warning @ bundle.js:30824

bundle.js:10836 Warning: Failed prop type: The prop players is marked as required in Scoreboard, but its value is undefined. in Scoreboard (created by Connect(Scoreboard)) in Connect(Scoreboard) in Provider

bundle.js:33242 Uncaught TypeError: Cannot read property 'map' of undefined at Scoreboard.render (bundle.js:33242) at bundle.js:19683 at measureLifeCyclePerf (bundle.js:18963) at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (bundle.js:19682) at ReactCompositeComponentWrapper._renderValidatedComponent (bundle.js:19709) at ReactCompositeComponentWrapper.performInitialMount (bundle.js:19249) at ReactCompositeComponentWrapper.mountComponent (bundle.js:19145) at Object.mountComponent (bundle.js:17486) at ReactCompositeComponentWrapper.performInitialMount (bundle.js:19258) at ReactCompositeComponentWrapper.mountComponent (bundle.js:19145)

Calin Bogdan
Calin Bogdan
14,921 Points

The current implementation of mapStateToProps is equivalent to:

function mapStateToProps(state) {
    {
      players: state.players
    }
}

// current implementation
const mapStateToProps = (state) => {
  {
    players: state.players
  }
}

It doesn't return anything. If you check again, Guil used parentheses, not curly braces. This way, the arrow function knows that it has to return an object.

const mapStateToProps = (state) => (   <----
  {
    players: state.players
  }
)  <----

// the above is the same with:
function mapStateToProps(state) {
  return {
    players: state.players
  }
}

Calin, I might not know you but I know you have the right if not best attitude for a developer-an undying spirit to solve a problem/challenge. The error in the browser console has changed, the first error is gone. Leaving only the remaining two:

bundle.js:10836 Warning: Failed prop type: The prop players is marked as required in Scoreboard, but its value is undefined. in Scoreboard (created by Connect(Scoreboard)) in Connect(Scoreboard) in Provider

ncaught TypeError: Cannot read property 'map' of undefined at Scoreboard.render (bundle.js:33242) at bundle.js:19683 at measureLifeCyclePerf (bundle.js:18963) at ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext (bundle.js:19682) at ReactCompositeComponentWrapper._renderValidatedComponent (bundle.js:19709) at ReactCompositeComponentWrapper.performInitialMount (bundle.js:19249) at ReactCompositeComponentWrapper.mountComponent (bundle.js:19145) at Object.mountComponent (bundle.js:17486) at ReactCompositeComponentWrapper.performInitialMount (bundle.js:19258) at ReactCompositeComponentWrapper.mountComponent (bundle.js:19145)

From the error message I noticed that these errors have the same root problem i.e. the 'players' prop. Fixing it should eliminate both of them, killing two birds with a stone.

Calin Bogdan
Calin Bogdan
14,921 Points

It's undefined because the initialState is an array with no name. Therefore the players array couldn't be found. You should define the initialState in the player reducer as an object with an array named players inside:

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

// how it should be 
const initialState = {
  players: [
   {
        name: 'Jim Hoskins',
        score: 31,
    },
    {
        name: 'Andrew Chalkley',
        score: 20,
    },
    {
        name: 'Alena Holligan',
        score: 50,
    }
  ]
};

Brilliant Calin just brilliant! Finally the Scoreboard is being displayed but not fully. The list of players isn't displayed but every other thing is and there are no errors in the console. The stopwatch is working fine-start, stop and restart buttons. But once I try to add a player (to the empty list of players in the Scoreboard), those previous two errors pop up in the console and the player isn't added.

Calin Bogdan
Calin Bogdan
14,921 Points

Same story:

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

    });

either return the <Player> component or change the outside curly braces into parentheses:

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

    ));  <-----

Good one, now the user interface of the whole Scoreboard app is being displayed. But in the console there's an error:

warning.js:33 Warning: Failed prop type: Counter: prop type index is invalid; it must be a function, usually from React.PropTypes. in Counter (created by Player) in Player (created by Scoreboard) in div (created by Scoreboard) in div (created by Scoreboard) in Scoreboard (created by Connect(Scoreboard)) in Connect(Scoreboard) in Provider

And both the increment and decrement buttons aren't functional, whenever I press any of them this error pops up in the console:

player.js:44 Uncaught TypeError: state.map is not a function at Player (player.js:44) at computeNextEntry (<anonymous>:2:27469) at recomputeStates (<anonymous>:2:27769) at <anonymous>:2:31382 at Object.dispatch (createStore.js:178) at dispatch (<anonymous>:2:31875) at Object.changePlayerScore (bindActionCreators.js:7) at onClick (Counter.js:13) at HTMLUnknownElement.boundFunc (ReactErrorUtils.js:63)

Then when I try to add a new player, the usual two errors still pop up.

Calin Bogdan
Calin Bogdan
14,921 Points

I know that what I'm going to say might sound lazy, but there are a lot of errors and typos you missed when working along with Guil. Try doing the redux course again to be sure you understand everything. I'm helping with making the Scoreboard work, but our goal is to make you understand how it works and how you can fix potential errors.

You're right Calin, I didn't even realize I made so much syntax errors. Thank you very much for all the effort, you really spurred me to never give up no matter how messed up ones code is. You're a living legend bro, keep it up.