How to make api call in useReducer?

7.3k Views Asked by At

Here's a class component I'd like to refactor to a functional component using useReducer

export default class FootballMatchesData extends Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedYear: null,
      matchData: [],
      firstCall: false
    };
  }

  chooseYear = (year) => (e) => {
    this.setState({
      selectedYear: year
    })
    axios.get(`https://website?property=${year}`)
      .then(res => {
        this.setState({ matchData: res.data, firstCall: true })
      })
  }

  render() {
    ...
  }
}

I'm stuck at defining the 'CHOOSE_YEAR' case of my reducer. How would I define that case so that it:

  1. updates selectedYear
  2. makes an api call to https://website?property=${year}, then populates matchData
  3. updates firstCall

Here is my current refactor. https://codesandbox.io/s/gracious-pare-um66h?file=/src/FootballMatches.js

1

There are 1 best solutions below

8
On BEST ANSWER

You seem unfamiliar with the reducer pattern. Reducers are pure functions taking a state object and action to apply to that state, and returns the next state object. There are zero side-effects in reducer functions.

Use an useEffect hook to fetch the data when year updates in state. You may not want to also use anchor tags for the year list options since clicking that will likely try to navigate or reload the app/page.

const initialState = {
  selectedYear: null,
  competitions: [],
  firstCall: false
};

const footballReducer = (state, action) => {
  switch (action.type) {
    case "CHOOSE_YEAR":
      return {
        selectedYear: action.year, // <-- save year payload
        firstCall: true
      };

    case "FETCH_BY_YEAR_SUCCESS":
      return {
        ...state, // <-- copy existing state
        competitions: action.competitions // <-- save competitions payload
      };
    default:
      throw new Error();
  }
};

const FootballMatches = () => {
  const [state, dispatchFootball] = useReducer(footballReducer, initialState);

  const yearChooseHandler = (year) => {
    dispatchFootball({ type: "CHOOSE_YEAR", year });
  };

  useEffect(() => {
    if (state.year) { // <-- ensure year value is truthy since null on initial render
      axios.get(`https://website?property=${state.year}`).then((res) => { // <-- access state.year for URL
        dispatchFootball({
          type: "FETCH_BY_YEAR_SUCCESS",
          competitions: res.data,
          firstCall: true
        });
      }
    });
  }, [state.year]); // <-- year dependency

  let years = [2011, 2012, 2013, 2014, 2015, 2016, 2017];
  return (
    <div>
      <div>Select Year</div>
      <ul>
        {years.map((year, idx) => {
          return (
            <li
              onClick={() => yearChooseHandler(year)} // <-- fix callback so it isn't invoked immediately and cause infinite render looping
              key={idx}
            >
              {year}
            </li>
          );
        })}
      </ul>

      ...
    </div>
  );
};