0

So here is my problem: I fetch an API, here is the action:

const OPTIONS = {
  method: 'GET',
  headers: {
    'X-RapidAPI-Host': 'api-football-v1.p.rapidapi.com',
    'X-RapidAPI-Key': '44316d2130msh21fed66e06e6a24p1dd597jsnf2e92ca6ac85'
  }
};

export function setLeagues() {

  const countries =
    [
      {
        france: ["Ligue 1", "Ligue 2"]
      },
      {
        england: ["Premier League"]
      },
      {
        germany: ["Bundesliga 1"]
      }
    ];

  let leagues = [];

  countries.forEach((country) => {
    fetch(`https://api-football-v1.p.rapidapi.com/v2/leagues/current/${Object.keys(country)[0]}`, OPTIONS)
    .then(response => response.json())
    .then((data) => {
      Object.values(country)[0].forEach((league) => {
        data.api.leagues.filter((league_api) => {
          if(league_api.name === league) {
            leagues.push(league_api);
          }
        });
      });
    });
  });
  return {
    type: 'SET_LEAGUES',
    payload: leagues
  };
}

The leagues return a correct array of league's object. Here is the reducer :

export default function(state, action) {
  if (state === undefined) {
    return [];
  }

  if (action.type === 'SET_LEAGUES') {
    return action.payload;
  } else {
    return state;
  }
}

And finally my container :

class LeaguesLabel extends Component {

  componentDidMount() {
    this.props.setLeagues()
  }

  render() {
    console.log(this.props.leagues);
    return(
      <div>
        <ul>
          {
            this.props.leagues.map((league) => {
              return(
                  <li>
                    {league.name}
                  </li>
              );
            })
          }
        </ul>
      </div>
    );
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    { setLeagues: setLeagues },
    dispatch
  );
}

function mapStateToProps(state) {
  return {
    leagues: state.leagues
  }
}


export default connect(mapStateToProps, mapDispatchToProps)(LeaguesLabel);

Of course I import setLeague into this container.

Nothing is displayed while my console.log(this.props.leagues) displayed a good Array of object who contain a "league".

thank you in advance for your help !

LucasDub
  • 11
  • 4
  • 1
    Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Jared Smith Mar 05 '20 at 15:08

2 Answers2

0

The problem is that you're calling an asynchronous function and return your result outside its perimeter.

What is happening it that the return statement is called before receiving the answer for the API call.

Therefore, the list will be empty...

You should read about asynchronous functions here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

On top of that, in order to use async functions in redux actions, you should use redux thunk.

Good example here -> https://appdividend.com/2018/10/03/redux-thunk-tutorial-example/

Charles dB
  • 619
  • 5
  • 12
  • Ty for your answer, but i don't understand.. My asynchronous function is setLeague ? If yes why the return is called before the answer of the API ? – LucasDub Mar 05 '20 at 15:32
  • First of all, in your redux action you should use redux-thunk middleware to handle async functions. It will not work without it. Secondly, I would try to call all all leagues in one call if enabled by the API or create a child react component where each one is responsible to make the api call for its league – Charles dB Mar 05 '20 at 15:39
  • I use a middleware : const middlewares = applyMiddleware(reduxPromise, logger); store={createStore(reducers, {}, middlewares)} – LucasDub Mar 05 '20 at 15:47
  • Good that you already have the code to add middleware. Redux-thunk is an additional middleware that enables async calls in your redux actions, I'll update my answer with an example – Charles dB Mar 05 '20 at 15:48
0

The other answer is right but to expand on that, think about your function like this

First it processes this block and so it iterates over the countries and start doing the fetch requests:

countries.forEach((country) => {
    fetch(`https://api-football-v1.p.rapidapi.com/v2/leagues/current/${Object.keys(country)[0]}`, OPTIONS)
    .then(response => response.json())
    .then((data) => {
      Object.values(country)[0].forEach((league) => {
        data.api.leagues.filter((league_api) => {
          if(league_api.name === league) {
            leagues.push(league_api);
          }
        });
      });
    });
  });

Next, before the previous work as had a chance to do anything, it will process the following block:

  return {
    type: 'SET_LEAGUES',
    payload: leagues
  };

So it has started to run your countries.forEach but the fetch promises are resolving outside of this function so before any data has been pushed into your array, you are already returning it.

Try making use of the await/async syntax, it will make it a lot easier to deal with trying to wrangle promises, something like:

export async function setLeagues() {
  const countries = [
    {
      name: "france",
      leagues: ["Ligue 1", "Ligue 2"],
    },
    {
      name: "england",
      leagues: ["Premier League"],
    },
    {
      name: "germany",
      leagues: ["Bundesliga 1"],
    },
  ]

  let payload = []

  for (const country of countries) {
    const { name, leagues } = country
    const response = await fetch(
      `https://api-football-v1.p.rapidapi.com/v2/leagues/current/${name}`,
      OPTIONS
    )
    const data = await response.json()

    for (const apiLeague of data.api.leagues) {
      if (leagues.includes(apiLeague.name)) {
        payload.push(apiLeague)
      }
    }
  }
  return {
    type: "SET_LEAGUES",
    payload,
  }
}
Tom Finney
  • 2,670
  • 18
  • 12
  • Hi ! Thank you for your answer. I understand the reasoning. I tested your code by analyzing it, but when you console.log the "payload" before the return in setLeagues(), he's empty ... – LucasDub Mar 05 '20 at 16:42
  • Ok ok thats work !!! I just made a mistake when importing promiseMiddleware from 'redux-promise'. Thank you very much, I understood my error and you helped me a lot. Thank you !! – LucasDub Mar 08 '20 at 10:28