Help with creating a custom input component to call the Poke API and display the ID and image of a Pokemon.

5 replies
Last updated: Jun 16, 2021
Im trying to make a custom input component that takes in a name from the input text field, calls the poke api, then i’m trying to then output the id from that data and then create an image preview of the pokemon from a a url based upon what the ID that is returned is in what I think will be an avatar from the sanity ui (haven’t even got to that bit given I cant get over my first hurdle!!). I created it in the input component as an object using User’s alpha cli , but i’m not entirely sure how I update the values correctly because I don’t fully understand the field patch event. Im not great at explaining so i’ve included some code (and Im sorry if Ive gone about how I do this completely wrong in the first place, Im learning so please forgive me!)
getPokeData(value.pokemonName);

  async function getPokeData(pokemonName) {
    const pokeApi = `<https://pokeapi.co/api/v2/pokemon/${pokemonName}>`;
    const res = await fetch(pokeApi);
    const data = await res.json();
    const pokeId = data.id;
      
  }
above is my code for getting the data, please can someone help explain how I would set the data.id into my field I have created for the ID.
Jun 13, 2021, 1:28 PM
Hi User. In order to have your function affect the DOM, one solution would be to use
useEffect()
in React. If we don’t, we could run the fetch all day and the DOM would just say “Yeah, so?”
The biggest problem that comes to my mind is how can we control when the fetch is called? We probably don’t want to use
onChange
since we’d be trying to fetch
p
,
pi
,
pik
,
pika
,
pikac
, and
pikach
before finally fetching
pikachu
. We could add a button to let the user control when to fetch. We could debounce, waiting a certain amount of time until the user stops typing. Probably my favourite would be building in a dropdown that makes suggestions based on what’s typed so far. For simplicity, I used onBlur so when the user clicks or tabs off the input, the fetch is run. Probably not the most intuitive for the user, but anyway…
First, we would import everything needed for the custom input component:


import React, { useState, useEffect } from 'react';
import FormField from 'part:@sanity/components/formfields/default'
import {PatchEvent, set} from 'part:@sanity/form-builder/patch-event'
import {TextInput} from '@sanity/ui'
Then we would set up the component and establish state:


export const UserPokemon = React.forwardRef((props, ref) => {
  const { type, onChange, value } = props;
  const { title, description } = type;
  const [pokeName, setPokeName] = useState();
  const [pokeId, setPokeId] = useState();
We could write a function that does our fetch and sets the results in state:


  async function getPokeData(pokemonName) {
    if(pokemonName === undefined) return;
    const pokeApi = `<https://pokeapi.co/api/v2/pokemon/${pokemonName}>`;
    const res = await fetch(pokeApi);
    const data = await res.json();
    setPokeId(data.id);
    setPokeName(pokeName);
  }
Next we would want to create a function to render out the image:


  function renderPokemon() {
    if(pokeName === undefined || pokeName === '') return;
    return <img src={`<https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokeId}.png>`} alt="Pokemon" />
  }
We use
useEffect
to re-render any time pokeName changes:

  useEffect(() => {
    getPokeData(pokeName)
    renderPokemon()
  }, [pokeName]);
Finally, we render to the studio page and export the component:


  return (
    <>
      <FormField label={title} description={description}>
        <TextInput
          type="string"
          ref={ref}
          value={value}
          onChange={event => {onChange(PatchEvent.from(set(event.target.value)))}}
          onBlur={event => setPokeName(event.target.value)}
        />
      </FormField>
      <div>
        {renderPokemon()}
      </div>
    </>
  );
})

export default UserPokemon;
To implement this, I would put the code into a file somewhere in your studio (here it is in its entirety):


import React, { useState, useEffect } from 'react';
import FormField from 'part:@sanity/components/formfields/default'
import {PatchEvent, set} from 'part:@sanity/form-builder/patch-event'
import {TextInput} from '@sanity/ui'

export const UserPokemon = React.forwardRef((props, ref) => {
  const { type, onChange, value } = props;
  const { title, description } = type;
  const [pokeName, setPokeName] = useState();
  const [pokeId, setPokeId] = useState();

  async function getPokeData(pokemonName) {
    if(pokemonName === undefined) return;
    const pokeApi = `<https://pokeapi.co/api/v2/pokemon/${pokemonName}>`;
    const res = await fetch(pokeApi);
    const data = await res.json();
    setPokeId(data.id);
    setPokeName(pokeName);
  }

  function renderPokemon() {
    if(pokeName === undefined || pokeName === '') return;
    return <img src={`<https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokeId}.png>`} alt="Pokemon" />
  }

  useEffect(() => {
    getPokeData(pokeName)
    renderPokemon()
  }, [pokeName]);

  return (
    <>
      <FormField label={title} description={description}>
        <TextInput
          type="string"
          ref={ref}
          value={value}
          onChange={event => {onChange(PatchEvent.from(set(event.target.value)))}}
          onBlur={event => setPokeName(event.target.value)}
        />
      </FormField>
      <div>
        {renderPokemon()}
      </div>
    </>
  );
})

export default UserPokemon;
Then in your schema file, you would import this component from the file and then implement it in your schema using something like:


{
  name: 'someName',
  type: 'string',
  inputComponent: UserPokemon,
}
I might set a height or min-height on the pokemon div so that your fields don’t jump around when re-rendering. There’s also some error handling that could be improved. Hopefully this can get you started and please feel free to follow up with questions.
Jun 13, 2021, 7:27 PM
One more thing—the images are actually returned in
data
so you could probably improve this fetch by just getting that back rather than interpolating
pokeId
into an otherwise hard-coded URL.
Jun 13, 2021, 7:29 PM
Ah what a donut I am. I would have used the usestate and use effect as I’ve done a full Pokédex project with this in react before but I misheard in one of the livestreams saying that use state wasn’t needed and thought the patch thing was used instead so I thought it was meant to be done without the usestate hook 🤦‍♀️ feel really stupid now. Thank you for your help. Will take a proper look tomorrow and redo all my code :)
Jun 13, 2021, 8:02 PM
No, don't feel stupid! There are plenty of ways to get this done and I wouldn't say any are obvious. React asks for a lot to get things done that seem like they should just work—one of the reasons I've been enjoying Svelte lately.
Jun 13, 2021, 8:28 PM
I forgot to say thanks for your help, I got it to work the way you suggested. Im guessing that because the results are generating the div afterwards, there is no way to save to the content lake this way? Been trying all week to get it to work the way I wanted it to so that I could enter the competition and instead i’ve ended up going in circles lol
Jun 16, 2021, 12:51 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?