2

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?

SatelBill
  • 641
  • 2
  • 12
  • 28
  • Rename question to: How to use component will update in react functional component. i.e. you need to use component will update with whatever hook you are trying to get working. Also it seems like not very react like. – SharpInnovativeTechnologies Dec 16 '21 at 03:56
  • It looks like you have a fundamental misunderstanding of how React and hooks work. I advise you to go back and read the documentation. https://reactjs.org/docs/hooks-rules.html – Benjamin Dec 16 '21 at 04:05
  • I echo @Benjamin comment. From the documentation it's quite clear that what the OP is asking is prohibited. – Muteshi Dec 16 '21 at 07:27

4 Answers4

8

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);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Thank you for your kind answer. well, my final purpose is that I hope to get result from here. => `const wallet = useWallet()`. And I hope to use this `wallet` in the class component. Is that possible? I wrote the functional component example just for test. I need to use class component. I'd like to know how to use `useWallet()` in the class component. – SatelBill Dec 16 '21 at 07:41
  • @SatelBill You can't use React hooks in class components, but you could create a `withWallet` Higher Order Component that can use the hook and pass the `wallet` value as a prop to your class component. If that sounds like something that could work for you and you are interested in I can update my answer. – Drew Reese Dec 16 '21 at 07:46
  • 1
    I would really appreciate it if you could repost your answer with the use of HOC. Thanks. – SatelBill Dec 16 '21 at 09:04
4

You can't call react hook inside a class component.

According to ReactJS Doc you can combine the functionality.

You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.

TheRakeshPurohit
  • 551
  • 1
  • 6
  • 22
-1

GetBlockId Is Not a React Functional Component. There is no return method; hence it will throw an error saying that you can't use a hook in a non Functional Component. Change this function to a functional component (via returning a JSX component) and it should work.

NOTE that your getBlockId function is recursive and will fail.

According to the docs. In your class component (parent component). You will want to use the UseWalletProvider and in your functional component use the hook.

Here is an example (untested), hopefully that will get you on your way.

import React, {useState} from 'react';
import { useWallet, UseWalletProvider } from 'use-wallet';
import { ethers } from 'ethers';
import { hash } from './hash'
function App() {
  const wallet = useWallet()
  const blockNumber = wallet.getBlockNumber()
  const [blockId, setBlockId] = useState('')
  useEffect(()=>{
    const getBlockId = async() => {
      const { ethereum, connect } = wallet;
      const ethersProvider = new ethers.providers.Web3Provider(ethereum);
      return await ethersProvider.getTransaction(hash);
    }
    setBlockId(getBlockId());
  },[]);
  //Note that now blockId contains the blockId that you get.
  //You can use it with child components etc.
  return (
    <div>
        <p>{blockId}</p>
    </div>
  );
}

function Index() {
  return (
    <UseWalletProvider
    chainId={1}
    connectors={{
      // This is how connectors get configured
      portis: { dAppId: 'my-dapp-id-123-xyz' },
    }}
  >
    <App />
  </UseWalletProvider>
  );
}
-2

try the following code, you'd better not using useXXX inside an function which in a functional component,

export function App() {
  const wallet = useWallet();
  const getBlockId = useCallback(() => {
      console.log(wallet);
      const { ethereum, connect } = wallet;
      const ethersProvider = new providers.Web3Provider(ethereum);
      const { blockNumber } = await ethersProvider.getTransaction(hash);
      console.log(blockNumber);
  }, [wallet]);
  useEffect(() => {
    getBlockId()
  }, []);

  return <div>
    <h1>{wallet}</h1>
  </div>
}
lam
  • 545
  • 1
  • 3
  • 10