Search code examples
node.jsreactjsdatabasesolana

React and Easybase - Invalid hook call. Hooks can only be called inside of the body of a function component


I am trying to use React and Easybase (database). I'm having some issues however.

This is in the SolanaSignature.tsx file.

import { useWallet } from '@solana/wallet-adapter-react';
import bs58 from 'bs58';
import React, { FC, useCallback } from 'react';
import ReactDOM from 'react-dom';
import { sign } from 'tweetnacl';
import AddUser from './mainstorage';


export const SignMessageButton : FC = () => {
    const { publicKey, signMessage } = useWallet();
    
    const onClick = useCallback(async () => {
        try {
            if (!publicKey) throw new Error('Wallet not connected!');
            if (!signMessage) throw new Error('Wallet does not support message signing! Please use a wallet such as Phantom or Solflare! NOTE: Some Ledgers wallets are not supported!');

            const message = new TextEncoder().encode('Omega Protocol - Signature verification for Bold Badgers.');
            const signature = await signMessage(message);
            if (!sign.detached.verify(message, signature, publicKey.toBytes())) throw new Error('Invalid signature!');
            //alert(`Message signature: ${bs58.encode(signature)}`);
          AddUser();

        } catch (error: any) {
            alert(`Signing failed: ${error?.message}`);
        }
    }, [publicKey, signMessage]);
    return signMessage ? (<button className="wallet-adapter-button wallet-adapter-button-trigger shine" onClick={onClick} disabled={!publicKey}>Verify</button>) : null;       
};

and then the mainstorage file:

import { useEffect } from 'react';
import { useEasybase } from 'easybase-react';

const AddUser = () => {
  const { db } = useEasybase();

  useEffect(() => {
    db('OMEGABB').insert({ walletid: "test", discordid: "test", signature: "test", valid: false, lastvalid: new Date() }).one()
      .then(() => console.log("Success!"));
  }, [])

  return (
    {/* ... */}
  );
}

export default AddUser;

What is happening however when I click the button is that it comes up with a warning: Hooks can only be called inside the body of a function component.

This does work in the initial index file (aka the parent file) but does not work here. Right now this is only a dummy/test but trying to get it writing to the database.

Thanks!


Solution

  • As per React's documentation:

    Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns.

    Currently, you're attempting to call a hook inside the onClick handler - AddUser is a custom hook since it also uses hooks and the better name for it should be useAddUser.

    I suggest to make some improvements by returning a function from your custom hook that you can call to add a new user, e.g.:

    export const useAddUser = () => {
      const {db} = useEasybase()
    
      const addUser = React.useCallback(() => {
        db('OMEGABB')
          .insert(/*...*/)
          .then(/*...*/)
          .catch(/*...*/)
      }, [db])
    
      return {
        addUser,
        /*...*/
      }
    }
    

    Then, you can use useAddUser in the following way:

    const {useAddUser} from './mainstorage'
    
    const SignMessageButton: FC = () => {
      const {publicKey, signMessage} = useWallet()
      const {addUser} = useAddUser();
    
      const onClick = React.useCallback(
        async () => {
          try {
            // ...
            addUser()
          } catch (error) {/*...*/}
        }, 
        [publicKey, signMessage, addUser]
      )
    
      /*...*/
    }