0

I am learning React hooks and made a project to display a random Pokemon name through https://pokeapi.co/docs/v2#pokemon by calling a random Pokemon ID. I'm not quite sure how to trigger the useEffect to fetch after clicking on the button.

import './App.css';
import { useState, useEffect } from 'react';


  function getNewPokemonId(pokemon){
  pokemon =  Math.floor(Math.random() * 1011);
  console.log(pokemon)
  return pokemon;
}

function App() {
  const [pokemon , setNewPokemon] = useState(1);


  useEffect(() => {
    console.log("render");
    const pokemonID = getNewPokemonId()
    fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonID}`)
    .then((response) => response.json())
    .then((json) =>
      setNewPokemon(json)
  );
  
  }, []);


  return (
    <main>
      <h1>
        Random Pokemon Generator
      </h1>
      <section>
        <button onClick={() => setNewPokemon(getNewPokemonId(pokemon))}>
          New Pokemon
        </button>
        <h3>{pokemon?.name}</h3>
      </section>

    </main>
  );
}

export default App;

After clicking on the button I don't get a new Pokemon name instead it is blank.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • at the end of the useEffect in the [], do this: [pokemon], then the useEffect will trigger every time that variable changes – Mat Jul 06 '23 at 18:30
  • State variables are the ones that modify the GUI once they are updated. While it is not obligatory, I suggest to use a prefix to differentiate state variables as st_variableName – Jose Cabrera Zuniga Jul 07 '23 at 14:43

4 Answers4

1

You can define a function to fetch the data for a Pokemon by id and call it on mount and in the click event handler.

function App() {
  const [pokemon, setNewPokemon] = useState();
  const getPokemon = id => fetch(`https://pokeapi.co/api/v2/pokemon/${id}`).then(res => res.json()).then(d => setNewPokemon(d));
  useEffect(() => {
    console.log("render");
    getPokemon(getNewPokemonId());
  }, []);

  return (
    <main>
      <h1>Random Pokemon Generator</h1>
      <section>
        <button onClick={() => getPokemon(getNewPokemonId())}>
          New Pokemon
        </button>
        <h3>{pokemon?.name}</h3>
      </section>
    </main>
  );
}
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
1

I created a sandbox with this app working:

Sandbox https://codesandbox.io/s/holy-morning-tkwtpv

import "./styles.css";
import { useState, useEffect } from "react";

function getNewPokemonId() {
  const pokemon = Math.floor(Math.random() * 1011);
  console.log(pokemon);
  return pokemon;
}

export default function App() {
  const [pokemon, setNewPokemon] = useState(1);

  const fetchPokemon = async () => {
    const response = await fetch(
      `https://pokeapi.co/api/v2/pokemon/${getNewPokemonId()}`
    );
    const data = await response.json();
    setNewPokemon(data);
  };

  useEffect(() => {
    fetchPokemon();
  }, []);

  const handleOnClick = () => {
    fetchPokemon();
  };

  return (
    <div className="App">
      <h1>Random Pokemon Generator</h1>
      <section>
        <button onClick={handleOnClick}>New Pokemon</button>
        <h3>{pokemon?.name}</h3>
      </section>
    </div>
  );
}

I hope that this could help

  • this works perfectly thank you! Just so I understand it correctly can you explain how this `useEffect` is getting triggered in this case? Is it because we are calling that `fetchpokemon()` function? – Taha Kamran Jul 06 '23 at 20:07
  • 1
    Yes exactly, the useEffect will running one time with empty dependency array, so this fetchPokemon will be called one time and will update the state. So, when u click, the fetch will be called again with the clickHandler – Fabio Ribeiro de Carvalho Jul 06 '23 at 23:48
0

having [] at the end of the useEffect will mean it will only trigger once at the start of the page load. having this: [pokemon] will make the useEffect run every time the variable changes.

Mat
  • 405
  • 4
  • 12
  • 1
    I have tried this previously and this will just create an infinite loop since the `pokemon` will never stop changing. – Taha Kamran Jul 06 '23 at 20:05
0

It' not fool-proof, but it's a start. You should store the Pokémon data into the state, not the ID. Make sure you properly set the state as well.

Note

If you notice, the IDs jump from 1010 to 10001.

So the random range should technically be: 1-1010, 10001-10271.


Example

Here is an basic working example of a Pokémon randomizer. It's fairly responsive.

const { useCallback, useEffect, useState } = React;

// Better PRNG than Math.random
// Source: https://stackoverflow.com/a/47593316/1762224
const mulberry32 = (seed) =>
  () => {
    let t = seed += 0x6D2B79F5;
    t = Math.imul(t ^ t >>> 15, t | 1);
    t ^= t + Math.imul(t ^ t >>> 7, t | 61);
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  }

const rand = mulberry32(performance.now()); // Seeded with current time

const randomInt = (min, max) => Math.floor(rand() * (max - min + 1)) + min;

const toTitleCase = (str = '') =>
  str.replace(/\w\S*/g, (txt) =>
    txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());

// Refer to: https://pokeapi.co/api/v2/pokemon?offset=0&limit=2000
const ranges = [[1, 1010], [10001, 10271]]; // Distribution: (1010, 271)

const getRandomId = () => {
  const which = randomInt(1, 5);
  const range = ranges[which === 5 ? 1 : 0]; // 20% for range #2
  return randomInt(...range);
};

const apiBaseUrl = 'https://pokeapi.co/api/v2';

const App = () => {
  const [pokemonData, setPokemonData] = useState({ id: 0, name: '', sprites: [] });
  const [loading, setLoading] = useState(false);
  const [errorText, setErrorText] = useState('');
  
  const randomize = useCallback(() => {
    setErrorText('');
    setLoading(true);
    const randId = getRandomId();
    fetch(`${apiBaseUrl}/pokemon/${randId}`)
      .then(res => res.json())
      .then(data => {
        setPokemonData(data);
      })
      .catch(() => {
        setErrorText(`Pokemon with ID of ${randId} not found!`);
      })
      .finally(() => setLoading(false));
  }, []);
  
  useEffect(() => {
    randomize();
  }, []);
  
  return (
    <main>
      <h1>Random Pokemon Generator</h1>
      <section>
        <button disabled={loading} onClick={randomize}>New Pokemon</button>
        {errorText !== '' && <p style={{color:'red'}}>{errorText}</p>}
        <h3>[{pokemonData.id}] {toTitleCase(pokemonData.name)}</h3>
        <img src={pokemonData.sprites.front_default} />
      </section>
    </main>
  );
};

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132