frustrated
frustrated

Reputation: 11

problem with Hooks and Components: Error Invalid hook call

I'm having an issue getting a custom Hook working while using react Query. The code is fairly simple, as I'm just trying to debug the hook. I'm basically making an API call to return some data as below:

Response: {jsonrpc: '2.0', result: '0xdbcd4663d6c31e1b8f1f4391676e8d257599374652b670ea4658a9639c0e5c8d', id: 'get-assetidkey1'}

My custom hook is in a file called TestC.tsx, the content below:

import React from 'react';
import { useQuery } from "@tanstack/react-query";
import { getAssIdKey1 } from "./api/getassid_key1";

interface Data {
  id: string;
  hexstr: string;
  bytes: number[];
}

export const getAssetData = () => {

    const { data, status, error } = useQuery<Data>({
        queryKey: ["assetid2"],
        queryFn: getAssIdKey1,
        placeholderData: [{ id: "dffd", hexstr: "0x1234", bytes: [] }],
    });
    if (status === "loading") return <h1>Loading...</h1>;
    if (status === "error") return <h1>Error: {error?.message}</h1>;

    const assetIdHex = data && data.hexstr;
    const bytes = data && data.bytes;

    console.log('GetData bytes:', data.bytes);
    return { assetIdHex, bytes, status, error };
};

The content of App.tsx is below:

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { getAssetData } from "./TestC";


interface Data {
  id: string;
  hexstr: string;
  bytes: number[];
}


function App() {
  const [count, setCount] = useState(0)
  const queryClient = useQueryClient()


  const handleAssetId = () => {
    const { assetIdHex, bytes, status, error } = getAssetData();
    console.log('Asset Id hex', assetIdHex);

    return (
      <div>
        <h1>AssetId Key 1</h1>
        <ol>
          <li>AssetId: {assetIdHex}</li>
        </ol>
      </div>
    );
  };


  return (
    <div className="container">
      xffd
      <h1 className="title"> ReactJS (Vite) Quick Start </h1>
      <div>
      <button onClick={() => handleAssetId()}>
        Posts List 1
      </button>
      </div>
    </div>
  );
}

I'm unsure why I'm getting the following error. As far as I understand, I have created the component to call onClick, that calls the custom Hook.

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

Tried refactoring the code, but still get the same error. Moved the hook into the main code - this works, but runs every time the page is reloaded. I want to be able to control the API call with a button and then further process the data.

Upvotes: 0

Views: 521

Answers (1)

Ro Milton
Ro Milton

Reputation: 2536

It's failing because you're calling useQuery from inside the click handler handleAssetId, and that's against the rule of hooks as you can see in your error message.

Also, it doesn't make any sense to return markup from a click handler. Markup must be returned directly from the render function.

First of all, rename getAssetData because it's a custom hook and you should follow naming conventions

// rename this to useAssetData
export const useAssetData = () => {
    // ...
    return { assetIdHex, bytes, status, error };
};

Secondly, call the custom hook directly in the render function of a component. If you want to fetch after a button click, simply wrap the list in a separate component and display it after click.

function List() {
  const { assetIdHex, bytes, status, error } = useAssetData();  
  return (
     { status === "loading" && <h1>Loading...</h1> } // note this is 'pending' in v5
     { status === "error" &&  <h1>Error: {error?.message}</h1> }
     { status === "success" && 
       <div>
         <h1>AssetId Key 1</h1>
         <ol>
           <li>AssetId: {assetIdHex}</li>
        </ol>
       </div>
     }
  )
}

function App() {
  const [ showList, setShowList ] = useState(false)

  return (
    <div className="container">
      xffd
      <h1 className="title"> ReactJS (Vite) Quick Start </h1>
      <div>
      <button onClick={() => setShowList(true)}>
        Fetch Posts List 1
      </button>
      { showList && <List /> }
    </div>
  );
}

If you want to control which asset gets fetched, simply pass the relevant asset ID into <Lists> and useAssetData.

Upvotes: 1

Related Questions