2

I'm new to React and am trying to build an app which shuffles football players into two teams and am having difficulty with passing data from one component to another.

I have redux and react-redux installed.

In my reducer.js, I take a list of players and shuffle them, adding the shuffled list to state:

const shufflePlayers = (state) => {
  return {
    ...state,
    shuffledList: [
      ...state.playersList.sort(() => Math.random() - 0.5)
    ]
  }
}

Then in 'src/components/DisplayTeams.index.js', I map the 'shuffledList' array to props:

import { connect } from "react-redux";
import DisplayTeams from "./DisplayTeams";

const mapStateToProps = (state) => {
  return {
    shuffledList: state.shuffledList,
  };
};

export default connect(mapStateToProps)(DisplayTeams);

and finally, in 'src/components/DisplayTeams.js', I attempt to render the 'shuffledList' array in a list:

import React from 'react';
import '../../App.css';

const DisplayTeams = ({ shuffledList }) => (

  <div>
    <ul>
      {shuffledList.map((player, index) => (
        <li key={index}>{player.name}</li>
      ))}
    </ul>
  </div>

);

export default DisplayTeams;

but am getting TypeError: Cannot read property 'map' of undefined, indicating that the 'shuffledList' array is empty or not set at all.

Any help will be much appreciated!!

6
  • have you console.log(state) in mapState to see what it is? Commented Jun 25, 2020 at 8:43
  • 1
    This line shuffledList: state.shuffledList doesn't seem right at all, so yeah, just a problem of looking at how does your state look like. Commented Jun 25, 2020 at 8:44
  • 1
    and what is the initialstate of shuffledList? Commented Jun 25, 2020 at 8:45
  • 2
    Heads up, in the shufflePlayers function, shuffledList: [ ...state.playersList.sort() ] mutates the original state.playersList array - you'll want to make a copy before sorting as .sort() mutates the original array: shuffledList: [...state.playersList].sort() Commented Jun 25, 2020 at 8:49
  • 1
    state.shuffledList is a copy of state.playersList, you should not save values in the state that you can calculate from state, use selector instead. Commented Jun 25, 2020 at 8:50

3 Answers 3

1

Two things:

  1. You should add add an initial state, you can set it directly in the reducer file

     const initialState = {
         // other reducer parts here
         shuffledList: []
     }
    
  2. The reducer should check the action type, otherwise it would run at any action. Something like this:

     const shufflePlayers = (state = initialState, action) => {
        switch (action.type) {
    
           case actionTypes.SHUFFLE_LIST: {
               // use a new array, avoid mutating the previous state
               const sortedList = [...state.playersList].sort(() => Math.random() - 0.5)
    
               return {
                   ...state,
                   shuffledList: sortedList
               }
           }
    
     }
    
Sign up to request clarification or add additional context in comments.

Comments

1

You should not copy data in state, the list and shuffledList are the same data but shuffledList is a calculated result of list.

You can use a selector to calculate shuffled list from list instead to prevent it from re calculating on renders you can use reselect (should use that anyway) and memoize shuffled result as long as list doesn't change.

const { Provider, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;

const initialState = {
  list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
};
const reducer = (state = initialState) => state;
//selectors
const selectList = (state) => state.list;
//if state.list changes then it will shuffle again
const selectShuffledList = createSelector(
  [selectList],
  (list) => [...list].sort(() => Math.random() - 0.5)
);
const selectTeams = createSelector(
  [selectShuffledList, (_, size) => size],
  (shuffledList, teamSize) => {
    const teams = [];
    shuffledList.forEach((item, index) => {
      if (index % teamSize === 0) {
        teams.push([]);
      }
      teams[teams.length - 1].push(item);
    });
    return teams;
  }
);
const selectTeamsCurry = (teamSize) => (state) =>
  selectTeams(state, teamSize);
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(() => (n) => (a) => n(a))
  )
);
const App = () => {
  //you can re render app with setCount
  const [count, setCount] = React.useState(0);
  //setting count has no effect on teams because
  // state.list didn't change and selectShuffledList
  // will use memoized shuffled result
  const teams = useSelector(selectTeamsCurry(3));
  return (
    <div>
      <button onClick={() => setCount((w) => w + 1)}>
        re render {count}
      </button>
      <pre>{JSON.stringify(teams, undefined, 2)}</pre>
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>


<div id="root"></div>

Comments

0

The above code looks fine. You can check the shuffledList in the initial state and also in the Redux store while dispatching actions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.