3
votes

I have the following functional component. I did debugging and read some posts about react-redux and useEffect, but still have had no success. On initial render the state in my redux store is null but then changes to reflect new state with data. However, my react UI does not reflect this. I understand what the issue is, but I don't know exactly how to fix it. I could be doing things the wrong way as far as getting the data from my updated state in the redux store.

Here is my component :

const Games = (props) => {
    const [gamesData, setGamesData] = useState(null)
    const [gameData, setGameData] = useState(null)
    const [gameDate, setGameDate] = useState(new Date(2020, 2, 10))    
    const classes = GamesStyles()

    // infinite render if placed in 
    // useEffect array
    const {gamesProp} = props
    useEffect(() => {
        
        function requestGames() {
            var date = parseDate(gameDate)            
            try {                
                props.getGames(`${date}`)
                // prints null, even though state has changed
                 console.log(props.gamesProp)
                setGamesData(props.gamesProp)

            } catch (error) {
                console.log(error)
            }
        }    
        requestGames()
    }, [gameDate])
    

    // data has not been loaded yet
    if (gamesData == null) {
        return (
            <div>
                <Spinner />
            </div>
        )
    } else {
        console.log(gamesData)

        return (
           <div><p>Data has been loaded<p><div>
           {/* this is where i would change gameDate */}
        )
    }
}

const mapStateToProps = (state) => {
    return {
        gamesProp: state.gamesReducer.games,
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        getGames: (url) => dispatch(actions.getGames(url)),
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Games)

Here is my reducer

import {GET_GAMES} from '../actions/types'


const initialState = {
    games: null // what we're fetching from backend
}

export default function(state = initialState, action){
    switch(action.type){
        
        case GET_GAMES:
            // prints correct data from state
            //console.log(action.payload)
            
            return{
                ...state,                 
                games: action.payload
            }
           
        default:
            return state
    }
}

Here is my action

import axios from 'axios'
import {GET_GAMES} from './types'


// all our request go here

// GET GAMES
export const getGames = (date) => dispatch => {
    
    //console.log('Date', date)
    axios.get(`http://127.0.0.1:8000/games/${date}`)
    .then(res => {
        
        dispatch({            
            type: GET_GAMES,
            payload: res.data
        })
    }).catch(err => console.log(err))

}

my state after initial render

When I place the props from state in my dependencies array for useEffect, the state updates but results in an infinite render because the props are changing. This happens even if I destruct props.

Here is an image of my redux state after it is updated on the initial render.

1
Why are you using the state of your component, with redux you do not need to have a local state, you could just have a isLoading variable in your local state and render props.gameData when isLoading becomes falseA. Ecrubit

1 Answers

2
votes

You were running into issues because you were trying to set the state based off of data that was in a closure. The props.gamesProp within the useEffect you had would never update even when the parent data changed.

The reason why props.gamesProp was null in the effect is because in each render, your component essentially has a new instance of props, so when the useEffect runs, the version of props that the inner part of the useEffect sees is whatever existed at that render.

Any function inside a component, including event handlers and effects, “sees” the props and state from the render it was created in.

https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function

Unless you have to modify gamesState within your component, I highly recommend that you don't duplicate the prop to the state.

I'd also recommend using useDispatch and useSelector instead of connect for function components.

Here's some modifications to your component based on what I see in it currently and what I've just described:

import { useDispatch, useSelector } from 'react-redux';

const Games = (props) => {
  const gamesData = useSelector((state) => state.gamesReducer.games);
  const dispatch = useDispatch();
  const [gameData, setGameData] = useState(null);
  const [gameDate, setGameDate] = useState(new Date(2020, 2, 10));
  const classes = GamesStyles();

  // infinite render if placed in
  // useEffect array
  useEffect(() => {
    const date = parseDate(gameDate);
    try {
      dispatch(actions.getGames(`${date}`));
    } catch (error) {
      console.log(error);
    }
  }, [gameDate, dispatch]);

  // data has not been loaded yet
  if (gamesData == null) {
    return (
      <div>
        <Spinner />
      </div>
    );
  } else {
    console.log(gamesData);

    return (
      <div>
        <p>Data has been loaded</p>
      </div>
      // this is where i would change gameDate
    );
  }
};

export default Games;

If you need to derive your state from your props, here's what the React Documentation on hooks has to say:

https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops