Can someone show me what's wrong with my Redux state?

Viewed 45

EDIT

The component that state is not rendering in is called TournamentShow, which calls the state and whatever functions I need to use for the Show page.

Nested within it is a conditional to call one of 3 pages, based on

Tournament.Status === "Open",

Tournament.Status === "Closed", and

Tournament.Status === "Complete"

Tournament Show:

import React, { Component } from 'react';
import { SignUpPage, HostUI, StartBracket, Results } from './TournamentScreens';
import {
    showTournament,
    addParticipant,
    closeTournament,
    shuffleParticipants
} from '../../actions/tournamentActions';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Spinner } from 'reactstrap';

class TournamentShow extends Component {
    constructor(props) {
        super(props);
        this.onSignUp = this.onSignUp.bind(this);
        this.onStartTournament = this.onStartTournament.bind(this);
        this.onShuffleParticipants = this.onShuffleParticipants.bind(this);
    };

    componentDidMount() {
        const id = this.props.match.params.id;
        this.props.showTournament(id);
    };

    static propTypes = {
        tournament: PropTypes.object.isRequired,
        auth: PropTypes.object.isRequired
    };

    onSignUp(tournamentId, user) {
        this.props.addParticipant(tournamentId, user);
    };

    onShuffleParticipants(array) {
        let currentIndex = array.length, temporaryValue, randomIndex;   
        while(0 !== currentIndex) {
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;  
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }   
        return array;
    };

    onStartTournament(tourneyId) {
        const { participants } = this.props.tournament.showTournament;

        // Randomize participants
        let reorderedParticipants = [];
        const shuffledParticipants = this.onShuffleParticipants(participants);
        shuffledParticipants.forEach(participant => {
            reorderedParticipants.push(participant);
        });

        // Send new participants list to backend
        this.props.shuffleParticipants(tourneyId, reorderedParticipants);

        // Set Status to "Closed"
        this.props.closeTournament(tourneyId);
    };

    render() {
        console.log(this.props.tournament)
        const loading = this.props.tournament.loading || !this.props.tournament.showTournament;

        if(loading) {
            return <Spinner color="light" /> 
        } else {
            if(this.props.tournament.showTournament.status === "Complete") {
                return (
                    <Results />
                );
            } else if(this.props.tournament.showTournament.status === "Closed") {
                return (
                    <div>
                        <HostUI
                            tournament={this.props.tournament.showTournament}
                        />
                        <StartBracket
                            tournament={this.props.tournament.showTournament}
                        />
                    </div>
                );
            } else {
                return (
                    <SignUpPage
                        tournament={this.props.tournament.showTournament}
                        auth={this.props.auth}
                        onSignUp={this.onSignUp}
                        onStartTournament={this.onStartTournament}
                    />
                );
            }
        };
    };
};

const mapStateToProps = state => ({
    tournament: state.tournament,
    auth: state.auth
});

export default connect(mapStateToProps, 
    { showTournament, addParticipant, closeTournament, shuffleParticipants }
)(TournamentShow);

Tournament Show Screens:

import React from 'react';
import moment from 'moment';
import { TournamentSignUp, StartTournament } from './resources/buttons';
import { TournamentRules } from './resources/rulesets';
import { Button } from 'reactstrap';
import { Link } from 'react-router-dom';


// Status === "Open"
export const SignUpPage = ({ tournament, auth, onSignUp, onStartTournament }) => {
};


// Status === "Closed"
export const HostUI = ({ tournament }) => {
    const { players } = tournament.bracket;

    return (
        <div style={{color:"lightgrey"}}>
            <h1>Host UI</h1>
            {
                players && players.map(player => (
                    <div>
                        {player.username}
                    </div>
                ))
            }
        </div>
    );
};


export const StartBracket = ({ tournament }) => {
    const { title, hostedBy, participants } = tournament;

  return (
    <div className="text-center" style={{color:"lightgrey", backgroundColor: "#333333"}}>
      <h1>{ title }</h1>
      <h4>By { hostedBy }</h4>
      <h4>{participants && participants.length}-player bracket</h4>
      <br /><Link to="/">Back to Tournaments main page</Link>
    </div>
  );
};


// Status === "Complete"
export const Results = () => {
};

Status===Closed shows both of those central components.

HostUI renders just the players array (which had just been updated right before the status switch/re-render)

StartBracket shows stuff from ShowTournament, which is all data that had already been set in the state

ORIGINAL --------------------------

I'll mark with // comment which case does not work

import { 
    GET_TOURNAMENTS, 
    SHOW_TOURNAMENT, 
    ADD_TOURNAMENT,
    ADD_TOURNAMENT_FAIL,
    EDIT_TOURNAMENT,
    EDIT_TOURNAMENT_FAIL,
    DELETE_TOURNAMENT, 
    TOURNAMENTS_LOADING, 
    TOURNAMENT_LOADING,
    USER_JOINS_TOURNAMENT, 
    TOURNAMENT_SIGN_UP_FAIL,
    TOURNAMENT_STATUS_UPDATE,
    TOURNAMENT_STATUS_FAILED,
    SHUFFLE_PARTICIPANTS,
    SHUFFLE_FAILED
} from '../actions/types';


const initialState = {
    tournaments: [],
    showTournament: {},
    loading: false,
};

export default function(state = initialState, action) {
    switch(action.type) {
        case GET_TOURNAMENTS:
            return {
                ...state,
                tournaments: action.payload,
                loading: false
            };

        case SHOW_TOURNAMENT:
            return {
                ...state,
                showTournament: action.payload,
                loading: false
            };

        case ADD_TOURNAMENT:
            return {
                ...state,
                tournaments: [action.payload, ...state.tournaments]
            };

        case DELETE_TOURNAMENT:
            return {
                ...state,
                tournaments: state.tournaments.filter(tournament => tournament._id !== action.payload)
            };

        case TOURNAMENTS_LOADING:
        case TOURNAMENT_LOADING:
            return {
                ...state,
                loading: true
            };

        case USER_JOINS_TOURNAMENT:
            return {
                ...state,
                ...state.showTournament.participants.push(action.payload)
            };

        case TOURNAMENT_STATUS_UPDATE:      // Occurs with SHUFFLE_PARTICIPANTS, which doesn't work
            return {
                ...state,
                ...state.showTournament.status = action.payload
            };

        case SHUFFLE_PARTICIPANTS:          // Does not work
            return {
                ...state,
                ...state.showTournament.bracket.players.push(action.payload)
            }

        case EDIT_TOURNAMENT:
        case ADD_TOURNAMENT_FAIL:
        case EDIT_TOURNAMENT_FAIL:
        case TOURNAMENT_SIGN_UP_FAIL:
        case TOURNAMENT_STATUS_FAILED:
        case SHUFFLE_FAILED:
            return {
                ...state,
            }

        default:
            return state;
    };
};

Most of that works.
The ones I'm sure I screwed up are TOURNAMENT_STATUS_UPDATE and SHUFFLE_PARTICIPANTS, although status update works as intended.

This is a tournament app whose show page renders 3 different components based on showTournament.status

...

    if(loading) {
        return <Spinner color="light" /> 
    } else {
        if(this.props.tournament.showTournament.status === "Complete") {
            return (
                <Results />
            );
        } else if(this.props.tournament.showTournament.status === "Closed") {
            return (
                <div>
                    <HostUI
                        tournament={this.props.tournament.showTournament}
                    />
                    <StartBracket
                        tournament={this.props.tournament.showTournament}
                    />
                </div>
            );
        } else {
            return (
                <SignUpPage
                    tournament={this.props.tournament.showTournament}
                    auth={this.props.auth}
                    onSignUp={this.onSignUp}
                    onStartTournament={this.onStartTournament}
                />
            );
        }
    };

Component button:

  • randomizes Tournament.participants and sends it to Tournament.bracket.players
  • sets Tournament.status === "Closed"

That updates the page and renders the Status: "Closed" page correctly.

The problem is, it only displays whatever I already had loaded in the state. (stuff from SHOW_TOURNAMENT)

The bracket.players array I sent the randomized user list to doesn't display until I refresh the page.

2 Answers

You need to shallow copy each level of state you are updating. Also, ...state.showTournament.bracket.players.push(action.payload) will simply attempt to spread in the return value of the push which is just the new length of the array. This isn't what you want.

case TOURNAMENT_STATUS_UPDATE:
  return {
    ...state,
    showTournament: {
      ...state.showTournament,
      status: action.payload,
    },
  };

case SHUFFLE_PARTICIPANTS:
  return {
    ...state,
    showTournament: {
      ...state.showTournament,
      bracket: {
        ...state.showTournatment.bracket,
        players: [...state.showTournament.bracket.players, ...action.payload], // spread payload array
      },
    },
  }

Drew's solution worked, it was just that, in order to pass an array into another array, the syntax is

players: [...state.showTournament.bracket.players. ...action.payload]

rather than players: [...state.showTournament.bracket.players, action.payload]

Good day

Related