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
A
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!)
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.
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; }
Jun 13, 2021, 1:28 PM
G
Hi User. In order to have your function affect the DOM, one solution would be to use
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
First, we would import everything needed for the custom input component:
Then we would set up the component and establish state:
We could write a function that does our fetch and sets the results in state:
Next we would want to create a function to render out the image:
We use
Finally, we render to the studio page and export the component:
To implement this, I would put the code into a file somewhere in your studio (here it is in its entirety):
Then in your schema file, you would import this component from the file and then implement it in your schema using something like:
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.
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
onChangesince we’d be trying to fetch
p,
pi,
pik,
pika,
pikac, and
pikachbefore 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'
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" /> }
useEffectto re-render any time pokeName changes:
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;
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;
{ name: 'someName', type: 'string', inputComponent: UserPokemon, }
Jun 13, 2021, 7:27 PM
G
One more thing—the images are actually returned in
dataso you could probably improve this fetch by just getting that back rather than interpolating
pokeIdinto an otherwise hard-coded URL.
Jun 13, 2021, 7:29 PM
A
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
G
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
A
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.