reactjstypescriptreact-hookswallet-connect

Is it possible to use React Hooks in class component by using HOC(Higher Order Component)?


Can I use the functional components in class components? I am going to call a function that is extracted from a functional component in class component. But it is giving errors like the following.

Unhandled Rejection (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

So I tried to call it in the functional component but even in the functional component, I got the same error as when I call it in class component.

Functional component

import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';

export function App() {
  useEffect(() => {
    async function GetBlockId() {
      const wallet = useWallet();
      console.log(wallet); // =====> This is not displaying.
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      console.log(blockNumber);
    };
    GetBlockId()
  }, []);

  return <div>
    <h1>{wallet}</h1>
  </div>
}

Class component

import React, { Component } from 'react'
import { GetBlockId } from './util';   // =====>> I hope to get result from here.
import { hash } from './hash'

export default class App extends Component {
    constructor(props) {
        super(props)
    }
    componentDidMount(): void {
        const blockNumber: any = GetBlockId(hash);
        console.log(blockNumber);
    }
    render() {
        return (
            <div>
                <h1>test</h1>
            </div>
        )
    }
}

util.tsx

import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
// import { Container } from './styles';

export function GetBlockId() {
  useEffect(() => {
    async function GetBlockId() {
      const wallet = useWallet();
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      return blockNumber;
    };
    GetBlockId()
  }, []);
}

enter image description here

So finally I hope to use "use-wallet" package in the class component. Is that possible? If yes, how to use useWallet hook in the class component?


Solution

  • React hooks are only compatible with React function components, they can't be used in class components at all. The issue with your first attempt is that you are trying to call a React hook in a callback, which breaks one of the Rules of Hooks.

    Rules of Hooks

    Only Call Hooks at the Top Level

    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. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

    Only Call Hooks from React Functions

    Don’t call Hooks from regular JavaScript functions. Instead, you can:

    • ✅ Call Hooks from React function components.
    • ✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).

    By following this rule, you ensure that all stateful logic in a component is clearly visible from its source code.

    You code is calling useWallet in a callback function passed to the useEffect hook. Note that this isn't the same thing as a custom Hook calling another hook.

    Move the useWallet hook call out into the function component body. This will close over the wallet value in the render scope and will be available/accessible in the useEffect hook callback. I'm assuming you still only want/need the useEffect hook to run once when the component mounts, so I'm leaving that aspect alone.

    import React, { useEffect } from 'react';
    import { UseWalletProvider, useWallet } from 'use-wallet';
    import { providers } from 'ethers';
    
    export function App() {
      const wallet = useWallet();
    
      useEffect(() => {
        console.log(wallet);
        const { ethereum, connect } = wallet;
    
        async function GetBlockId() {          
          const ethersProvider = new providers.Web3Provider(ethereum);
          const { blockNumber } = await ethersProvider.getTransaction(hash);
          console.log(blockNumber);
        };
    
        GetBlockId();
      }, []);
    
      return (
        <div>
          <h1>{wallet}</h1>
        </div>
      );
    }
    

    Update

    To use the useWallet hook with a class component I suggest creating a Higher Order Component that can use it and pass the wallet value as a prop.

    Example:

    const withWallet = Component => props => {
      const wallet = useWallet();
      return <Component {...props} wallet={wallet} />;
    };
    

    Decorate the class component and access via this.props.wallet

    class App extends Component {
      constructor(props) {
        super(props)
      }
      componentDidMount(): void {
        const { ethereum, connect } = this.props.wallet;
    
        ...
      }
      render() {
        return (
          ...
        );
      }
    }
    
    export default withWallet(App);