Async fetch switch function not returning case value

Hello!

I have a working function that is taking its switch expression from the state, and returns the desired case value correctly. See here:
let genFunction = (value) => {
let name = nameFunction(value)
if (!value[“game_indices”]) {
return null;
} else {
try {
switch(value[“game_indices”][0].version.name) {
case “red”:
return name + " first appeared in Generation I."
case “gold”:
return name + " first appeared in Generation II."
case “ruby”:
return name + " first appeared in Generation III."
case “diamond”:
return name + " first appeared in Generation IV."
case “black”:
return name + " first appeared in Generation V."
case “x”:
return name + " first appeared in Generation VI."
case “sun”:
return name + " first appeared in Generation VII."
case “sword”:
return name + " first appeared in Generation VIII."
default:
return null
}
} catch (err) {
console.error(err)
}
}
}

However I am trying to change this to fetch from a different source and use that data instead, however the switch statement is not returning the specified values, even though I can add a console.log and see that the switch statement is taking the values and going to the correct case. Seen here:
let genFunction = (value) => {
let name = nameFunction(value)
if (!value.id) {
return null
} else {
const getData = async () => {
const res = await fetch(“https://pokeapi.co/api/v2/pokemon-species/” + value.name)
const data = await res.json()
try {
switch(data.generation.name) {
case “generation-i”:
return name + " first appeared in Generation I."
case “generation-ii”:
return name + " first appeared in Generation II."
case “generation-iii”:
return name + " first appeared in Generation III."
case “generation-iv”:
return name + " first appeared in Generation IV."
case “generation-v”:
return name + " first appeared in Generation V."
case “generation-vi”:
return name + " first appeared in Generation VI."
case “generation-vii”:
return name + " first appeared in Generation VII."
case “generation-viii”:
return name + " first appeared in Generation VIII."
default:
return null
}
} catch (err) {
console.log(err)
}
}
getData()
}
}

The returned value is then being passed as a prop further in the code. As I said, the first block of code is working and returning correctly. Sorry for the lengthy code, please let me know if you can help! I always get great help from you everyone so thanks in advance!

It looks that you just need to move getData() before the try block, and also return the value that you get. I’d refactor the code like this (it’s a draft):

Mind that all the quotes you include have a weird format & may cause trouble.

const apiEndPoint = "https://pokeapi.co/api/v2/pokemon-species/"

//give it a useful name
const pokemonData = async (baseUrl, name) => {
const rawRes = await fetch(baseUrl + name);
const res = await rawRes.json();
return res // data
}

let pokemonGeneration = (baseUrl, value) => {
let name = nameFunction(value)
if (!value.id) return null 
// this is only executed if value isn't null
try {
const data = await getData(baseUrl, value.name); //await the async function
switch(data.generation.name) {
case “generation-i”:
return name + " first appeared in Generation I."
case “generation-ii”:
return name + " first appeared in Generation II."
case “generation-iii”:
return name + " first appeared in Generation III."
case “generation-iv”:
return name + " first appeared in Generation IV."
case “generation-v”:
return name + " first appeared in Generation V."
case “generation-vi”:
return name + " first appeared in Generation VI."
case “generation-vii”:
return name + " first appeared in Generation VII."
case “generation-viii”:
return name + " first appeared in Generation VIII."
default:
return null
}
} catch (err) {
console.log(err)
}}

@santimir Hello, thanks so much for your advice! I can see how this helps with the problem, however this line of code:
const data = await getData(baseUrl, value.name); //await the async function
that you have provided me with throws an error, I guess because it uses await outside of an async function?

Also you declared the const pokemonData, and then later used getData, are these supposed to be named the same? I see the same thing with apiEndPoint and baseUrl.

Sorry if I’m misunderstanding, I really appreciate your help!

Good catch. Yes, the first fn needs async (i just include the first bit…). Also, that way the function won’t block the execution from the rest of your script. Also I fixed the fn name as you pointed out.

let pokemonGeneration = async(baseUrl, value) => {
let name = nameFunction(value)
if (!value.id) return null 
// this is only executed if value isn't null
try {
const data = await pokemonData(baseUrl, value.name); //await the async 
...
1 Like

baseUrl is a general name because we are defining the function, not executing it. When executing the function it would look something like

pokemonGeneration(apiEndPoint, value)
//basically means baseUrl=apiEndPoint, value=value

@santimir Thanks for clearing that up! Although it’s created a new error, which I think is part of what I was originally dealing with. The new error is that either pokemonGeneration() or pokemonData() is returning a promise, rather than a string, and as such cannot be rendered. Any idea with this?

Yes, async functions always return a Promise object. Either you could wrap in another async function or use .then. I’d run the top async function like so:

pokemonGeneration(apiEndPoint, value).then(res=>{
//do something with res for example
console.log(res)
})

@santimir I see, even when I add the .then method to the function call, I am still getting the error for trying to return a promise.

Here are the current relevant snippets of code:

const getData = async (url, name) => {
const rawRes = await fetch(url + name);
const res = await rawRes.json();
console.log(res)
return res
}
let genFunction = async(value) => {
let name = nameFunction(value)
const baseUrl = “https://pokeapi.co/api/v2/pokemon-species/
if (!value.id) {
return null
} else {
try {
const data = await getData(baseUrl, value.name);
switch(data.generation.name) {
case “generation-i”:
return name + " first appeared in Generation I."
case “generation-ii”:
return name + " first appeared in Generation II."
case “generation-iii”:
return name + " first appeared in Generation III."
case “generation-iv”:
return name + " first appeared in Generation IV."
case “generation-v”:
return name + " first appeared in Generation V."
case “generation-vi”:
return name + " first appeared in Generation VI."
case “generation-vii”:
return name + " first appeared in Generation VII."
case “generation-viii”:
return name + " first appeared in Generation VIII."
default:
return null
}
} catch (err) {
console.log(err)
}
}
}

and the the function call is inside a react component:

<Display
name={nameFunction(this.state)}
picture={pictureFunction(this.state)}
type={typeFunction(this.state)}
generation={genFunction(this.state).then(res=>{console.log(res)})}
evolution={evolutionFunction(this.state)}
/>

Given something like this to remove that huge switch statement with the repeated code:

function pokédebut(name, gen) {
  if (!gen) {
    throw new Error("No generation specified");
  }

  gen = gen.split("-")[1];
  return `${name} first appeared in Generation ${gen.toUpperCase()}`;
}

console.assert(pokédebut("Bulbasaur", "generation-i") === "Bulbasaur first appeared in Generation I");

Then should just be:

const generation = async (name) => {
  const baseUrl = “https://pokeapi.co/api/v2/pokemon-species/ 1”;

  try {
    const res = await fetch(`baseurl/${name});
    const data = await res.json();
    return pokédebut(name, data?.generation?.name);
  } catch (err) {
    // do something with the error
  }
}

But the way you’re trying to use this in the component does not seem correct, so would need to see that code as well

1 Like

@DanCouper Thank you for your response and suggestion! I won’t have time to try that until I come home from work later but I’ll give it a try! Here is the link to the app.js file, I haven’t committed any of the earlier suggestions from this thread however. Also I have a feeling you will find more instances of “repeated” code:

If you have the time/patience please give it a look, I’m thankful for any help or criticism!

1 Like

@DanCouper Hello again,

I have made the changes you have suggested, but am still having the problem of the function returning a promise, and being unable to render. I have made a new branch on github showing the current code after having made your suggested changes. It can be seen here:

I appreciate your help so far, please let me know if you can offer any other help/ideas!

1 Like

Well, the issue is that that function returns a promise. Hence what you’re trying to render is just a promise object.

So think about what has to happen here:

  1. You have a form, which lets a user type the name of a pokemon.
  2. When they’ve typed that they hit submit, and a request is made to the pokedex URL with the name attached to it.
  3. When the response comes back, the data in it is used to populate the search result UI showing the info about that pokemon.

So a set of steps, one thing at a time.

To break this down further, ideally what you would normally do is this:

  1. There is some state representing the input value (the name of the pokemon species).
  2. When a user types the name, store that in the state.
  3. When a user submits the form, render a component for the search result
  4. Pass the name to that component so it can make the request
  5. If the request fails, have that component render some error message
  6. If the request succeeds, have that component render the result.

Some other issues:

  • You are currently using the species (pokemon-species/) endpoint to search on (I’ll ignore evo for the minute)
  • Species gets you name & generation, not types or image
  • The pokemon search endpoint (just pokemon/) gets you name, type/s and image, but not generation.

So there are different ways to do this, they all follow a similar pattern, but they differ in what exact order you want events to occur in.

You could search on Pokémon name. Then when you get that result, load in the component that has the results. And that component then takes result.species.url & hands it to another component which loads in species data (including the generation).

You could do it the opposite way, go species first.

You could make all of the linked requests in the same async call (get one result, make second request based on URL in that etc).

You could do any of the above and, instead of setting just data in state, have separate name, types, generation, imageUrl properties that you set.

So as an example, in App, I add some state: the name that’s been input by a user. I also want to indicate if they’ve submitted the form (ie they are making a search):

class App {
  state = {
    searchTerm: "",
    searchRequestMade: false,
  }

  // Whenever they type, update the search term.
  // They can't be searching at this point, so that goes to false.
  handleChange = (e) => {
    this.setState({
      searchTerm: e.target.value,
      searchRequestMade: false,
    });
  }

  // When they submit, set that flag
  handleSubmit = (e) => {
    e.preventDefault();
    this.setState({ searchRequestMade: true });
  }

  render() {
    return (<>
      <form id="searchForm" onSubmit={handleSubmit}>
        <input
          id="searchInput"
          onChange={handleChange}
          placeholder="search for a Pokémon here!"
          type="text"
          disabled={this.state.searchRequestMade}
        />
        <button id="searchButton" type="submit">Search</button>
      </form>
      { this.state.searchRequestMade && <SearchResult searchTerm={this.state.searchTerm} /> }
    </>)
  }
}

So I’ve now got something called SearchResult that I need:

class SearchResult {
  state = {
    data: null,
    error: null,
    loading: false,
  };

  async componentDidMount () {
    const url = `https://pokeapi.co/api/v2/pokemon/${this.props.searchTerm}`;
    this.setState({ loading: true });
    try {
      const res = await fetch(url);
      if (res.)
      const data = await res.json();
      this.setState({ data, loading: false });
    } catch (error) {
      this.setState({ error, loading: false });
    }
  }

  render () {
    if (this.state.loading) {
      return <LoadingComponent />;
    } else if (this.state.error) {
      return <ErrorComponent />
    } else if (this.state.data) {
      return <Display data={this.state.data} />
    } else {
      return null;
    }
  }
}

When they hit search, `SearchResult is mounted and handed the search term as a prop. When it mounts, it takes that search term and makes a request, populating its data/error state depending on the result.

Then depending on that result, it renders one of three components (loading, error and result related).

React tries to force you to move the data in one direction, in a series of stages, and have the UI work based on that. This can make it much much simpler to understand what’s happening, but it normally means you want to split things up into simple parts. Do {simplest thing}. If that thing works, render {this simple thing}. If it doesn’t, render {that simple thing} and so on.

So I do have a working example, though please don’t just copy it, it’s just there to try to help you understand what I’m describing here. I haven’t done some of the things you are (evolution for example). I’ve just hoyed it together, so it doesn’t work terribly well, but I think it’s ok as an example.

1 Like

@DanCouper Wow, I can’t thank you enough for typing all this up and creating the working example to boot, thank you so much! I’ll dig into this a bit more tomorrow when I have time to try and write my own version, but I just wanted to ask about different components each having their own state. That is something I haven’t tried before because I was under the impression there should ideally only be one instance of state at the base of any app. Am I wrong, and having more than one instance is commonly used/within best practices?

Thanks once again for the time and effort you’ve taken for me!

Not having any state is easiest, but it’s not very practical, so next best is state as close as you can get it to where it’s being used. The further away you move it the more complicated you’re going to make things, the more you’re going to have to write code that manages that, the harder it is going to be to read code, the more difficult it will be to debug and fix performance issues.

Always moving it right up to the top means you need to pass everything down in props (or use Context, which does the same thing) and means that literally any change will normally cause your entire app to render.

For some situations it is useful to keep some types of data at the top, can simplify things. If it it’s something like authentication then Context at the top level is ideal. For things involving lots of data/objects (eg may need to load in data from various sources and manage it and have it available throughout the app) there are state container solutions. Redux is the most prominent by a long way). Even then, you absolutely do not want everything in that state container. When Redux was used more oftenthan it is now there were a lot of libraries that did stuff like handle forms or navigation, and it’s a terrible idea.

Here is a link to an article on state colocation that is worth a read as well, it gives some more info and examples.

1 Like

Thanks to your suggestions and looking at the example you created I started the project over and was able to reach my previous point of functionality. The point I found most helpful was your idea of returning a “loading” state as a buffer for the fetch to return and for the data to populate. Also as I thought the multiple states makes it so much easier to organize the app structure. In addition to using template literals, it’s amazing how much the length of my code has shrunk due to this advice! Once again thank you so much for your help!

1 Like

This was great information for me rebuilding this project, thanks so much for providing this link!