reactjsreact-native

React Native Custom Hook call with Parameter Invalid Hook Call


I'm trying to use my custom hook when onPress is tapped, it receives a parameter but after calling it it returns invalid hook call. I want to call my UPDATED custom hook by calling a function that would pass the argument, I mean parameter, to my custom hook so it loads the chosen url.

My code:

    const PokemonListItem: React.FC<PokemonItemProps> = ({dataPokemon}) => {
      const [pokemon, setPokemon] = useState<IPokemonAttributes>();
      const [modalVisible, setModalVisible] = useState(false);
    
        const pokemonsData = (chosenPokemonUrl: string) => {
            const {data, isLoading} = usePokemonAttributes(chosenPokemonUrl);
            setPokemon(data);
            setModalVisible(true);
            console.log(data);
          };
        
          return (
            <View>
//this DOESNT work
              <TouchableOpacity onPress={() => pokemonsData(dataPokemon.url)}>
                <Text style={styles.pokemonListItem}>{dataPokemon.name}</Text>
              </TouchableOpacity>
            </View>
          );
        };

My custom hook:

const usePokemonAttributes = (pokemonUrl: string): IusePokemonAttributes => {
  const [isLoading, setLoading] = useState(false);
  //const [shouldRun, setShouldRun] = useState(true);
  const [data, setData] = useState<IPokemonAttributes>();
  const [error, setError] = useState('');
  const abortController = new AbortController();

  // const handleTap = () => {
  //   setShouldRun(!shouldRun);
  // };

  const fetchingPokemonAttributes = async () => {
    setLoading(true);
    try {
      const response = await fetch(`${urls.baseUrl + pokemonUrl}`, {
        signal: abortController.signal,
      });
      const json = await response.json();
      setData(await json);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchingPokemonAttributes();

    return () => {
      abortController.abort();
    };
  }, [pokemonUrl]);
  return {data, error, isLoading};
};

Solution

  • The Problem

    It's not working because your custom hook is not in the PokemonListItem component's body. With current implementation, it's a bit tricky to call the fetchingPokemonAttributes from the PokemonListItem component.

    Solution

    Instead of calling another function to call the fetchingPokemonAttributes, call it directly with onPress action.

    To achieve this, we need to export the fetchingPokemonAttributes from the custom hook to use it in PokemonListItem:

    First, in the custom hook:

    const usePokemonAttributes = () => {
      const [isLoading, setLoading] = useState(false);
      const [data, setData] = useState<IPokemonAttributes>();
      const [error, setError] = useState('');
      const abortController = new AbortController();
    
      const fetchingPokemonAttributes = async (pokemonUrl) => {
        setLoading(true);
        try {
          const response = await fetch(`${urls.baseUrl + pokemonUrl}`, {
            signal: abortController.signal,
          });
          const json = await response.json();
          setData(json); // -----> also remove the await from here !!
        } catch (error) {
          setError(error.message);
        } finally {
          setLoading(false);
        }
      };
    
      useEffect(() => {
        return () => {
          abortController.abort();
        };
      }, [pokemonUrl]);
    
      return {data, error, isLoading, fetchingPokemonAttributes};
    };
    

    Note: since the fetchingPokemonAttributes need the url, I removed the pokemonUrl from the custom hook and pass it directly with the fetchingPokemonAttributes function.

    Note: you added a await keyword in setData method. it's redundant.

    Second,, in the PokemonListItem, we need to call fetchingPokemonAttributes with onPress action. (don't forget to pass the URL here)

    const PokemonListItem: React.FC<PokemonItemProps> = ({dataPokemon}) => {
      const [pokemon, setPokemon] = useState<IPokemonAttributes>();
      const [modalVisible, setModalVisible] = useState(false);
      
      const {data, error, isLoading, fetchingPokemonAttributes} = usePokemonAttributes();
    
      useEffect(() => {
        if(data.length > 0 && !error) {
          setPokemon(data);
          setModalVisible(true);
        }
      }, [data, error])
            
      return (
        <View>
          <TouchableOpacity onPress={() => fetchingPokemonAttributes(dataPokemon.url)}>
            <Text style={styles.pokemonListItem}>{dataPokemon.name}</Text>
          </TouchableOpacity>
        </View>
      )
    }
    

    Now, with the onPress action, the fetchingPokemonAttributes will invoke and the custom hook returns data, error, isLoading. Now time to use them in the PokemonListItem with useEffect hook.

    As you see, I used some rules to check the conditions before updating the pokemon, you can define your custom condition here according to your requirement and data type.