Passing data between components in React/Redux

Viewed 1343

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!!

3 Answers

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

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>

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

Related