Home > Enterprise >  react function on context renders 4 console.logs plus infinite adding
react function on context renders 4 console.logs plus infinite adding

Time:10-28

I have this pokemon api app, and it haves some issues, firstly when i fetch pokemon, it keeps adding infinite x pokemon with 1 request. Secondly, when I try to type something again, the app literally freezes. Code below:

App:

import React from 'react'
import './App.css'
import {PokemonForm} from '@components/PokemonForm/PokemonForm'
import {PokemonProvider} from "@context/PokemonContext";
import {PokemonList} from "@components/PokemonForm/PokemonList";

const App: React.FC = () => {
    return (
        <PokemonProvider>
            <PokemonList/>
            <PokemonForm/>
        </PokemonProvider>
    )
}
export default App

Form: (passes data to useFetch)

import React from "react";                                                                                                                      
import {useFetchPokemon} from '@hooks/useFetchPokemon'                                                                                          
                                                                                                                                                
                                                                                                                                                
export const PokemonForm: React.FC = () => {                                                                                                    
    const {input, handleChange, handleSubmit} = useFetchPokemon()                                                                               
                                                                                                                                                
    return (                                                                                                                                    
        <form className="sfr-form" onSubmit={handleSubmit}>                                                                                     
            <input name="name" value={input.name} placeholder="boas" onChange={(e)=> handleChange(e)}/>                                         
            <button type="submit">fetch</button>                                                                                                
        </form>                                                                                                                                 
    );                                                                                                                                          
};     

Fetch Hook: (fetches the data)

import React, {useState, useEffect} from 'react'
import {usePokemon} from "@context/PokemonContext";

export const useFetchPokemon = () => {
    const [newPokemon, setNewPokemon] = useState<Pokemon | string | null>(() => '');
    const [input, setInput] = useState({name: '', submited: false})

    const {getPokemon} = usePokemon()
    getPokemon(newPokemon as Pokemon)

    useEffect(() => {
        const getData = async () => {
            try{
                const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${input.name}`).then((res)=>res.json())
                setNewPokemon({id: response.id, name: response.name, type:response.types[0].type.name, imageUrl: response.species.url})
            } catch (error) {
                console.log('error', error)
            }
        };
        getData();

        return () => {
            setNewPokemon('')
            setInput({name: '', submited: false})
        };
    }, [input.name, input.submited]);

    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        setInput({...input, name: event.target.value})
    }

    const handleSubmit = async (event: { preventDefault: () => void; }) => {
        event.preventDefault();
        setInput({...input, submited: true});
    }

    return {newPokemon, handleChange, handleSubmit, input}
}

useStorageHook (to storage):

import { useEffect, useState } from "react"

export function useLocalStorage<T>(key: string, initialValue: T | (() => T)) {
    const [value, setValue] = useState<T>(() => {
        const local = localStorage.getItem(key)
        if (local != null) return JSON.parse(local)

        return typeof initialValue === "function" ? (initialValue as () => T)() : initialValue
    })

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value))
    }, [key, value])

    return [value, setValue] as [typeof value, typeof setValue]
}

List:

import React from "react";
import {usePokemon} from "@context/PokemonContext";

export const PokemonList = () => {
    const {pokemon} = usePokemon()

    return (
        <div className="sfr-list">
            {pokemon && [pokemon].map((e, i) => (
                <div key={i}>
                      <pre>
                          {JSON.stringify(e, null, 2)}
                      </pre>
                </div>
            ))}
        </div>
    )
}

Context (sets the data to local storage):

import {createContext, ReactNode, useContext} from "react";
import {useLocalStorage} from "@hooks/useFetchStorage";

type PokemonContext = {
    pokemon: {};
    getPokemon: ({} : Pokemon) => void
}

const PokemonContext = createContext({} as PokemonContext)

export const usePokemon = () => {
    return useContext(PokemonContext)
}

export const PokemonProvider = ({children} : {children: ReactNode}) => {
    const [pokemon, setPokemon] = useLocalStorage<Pokemon[]>('pokemon', [{id: 123, name: 'mariomon', type: 'fogo', imageUrl: 'www.google.com'}]);

    const getPokemon = async (newlyPokemon : Pokemon) => {
        await newlyPokemon && setPokemon(currentPokemons => [...currentPokemons, newlyPokemon])
    }

    return <PokemonContext.Provider value={{getPokemon, pokemon}}>
        {children}
    </PokemonContext.Provider>
}
                                                                                                                                     

CodePudding user response:

The reason why your pokemon is added infinite times, is the following:

  1. useEffect in useFetchPokemon updates newPokemon state. This causes
  2. the hook useFetchPokemon to be re-rendered. During this render
  3. getPokemon is called with newPokemon, updating the state of PokemonProvider, which causes
  4. the context PokemonProvider to re-render, which in turn
  5. causes all children of PokemonProvider to re-render, which takes us back to 3) and we have a loop.

You don't want any calls to setState to be outside of a useEffect as that almost always will cause infinite loops.

  • Related