'UseDapp - useCall - How to get return value from Solidity function?

I am still quite new with react and solidity and don't know where to start. I can update a String and want to print it out after I changed it. But I want the String from the Blockchain. The return value is still 'undefined'.

My index.ts file:

export function MyString(contract: Contract) {
    const { state, send } = useContractFunction(contract, "setString", {});
    return { state, send };
}

export function GetMyString(contract: Contract) {
    const { value, error } = useCall({
        contract: contract,
        method: 'getString',
        args: []
    }) ?? {};

    if (error) {
        console.log("Error: ", error.message);
        return undefined;
    }
    console.log("value", value);
    return value;
}

My react component:

import React, { useState } from 'react';

import { useEthers } from "@usedapp/core";
import { ethers } from "ethers";
import myStringContractAbi from "./abi/myString.json";
import { myStringContractAddress } from "./contracts";
import { Contract } from "@ethersproject/contracts";

import { MyString, GetMyString } from "./hooks"

function App() {

  const { activateBrowserWallet, account, deactivate } = useEthers();

  const simpleContractInterface = new ethers.utils.Interface(myStringContractAbi);
  const contract = new Contract(myStringContractAddress, simpleContractInterface, ethers.getDefaultProvider(42));

  const { send, state } = MyString(contract);

  const value = GetMyString(contract);

  const [text, setText] = useState<string>("")
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const newText = event.target.value === "" ? "" : String(event.target.value)
    setText(newText)
  }

  const sendString = async () => {
     send(text);
  }

  const getContractString = async () => {
    console.log("APP VALUE", value);
    alert(value);
  }


  React.useEffect(() => {
    state.status === 'Exception' && console.log('State of set String: ', state.errorMessage);

    state.status === 'Success' && console.log('Set String successfully!');
  })

  return (
    <div>
      {!account && <button onClick={() => activateBrowserWallet()}> Connect </button>}
      {account && (
        <div>
          <button onClick={deactivate}>Disconnect</button>
        </div>
      )}
       <div>
          {account ? (
            <div>My Account Address: {account} </div>
          ) : (
            <div>Kein Account verbunden!</div>
          )}
        </div>

        <input onChange={handleChange} />
        <button onClick={sendString}>Call Function</button>

        <button onClick={getContractString}>Call Get String Function</button>
    </div>
  );
}

export default App;

Here is my Contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract MyString {
    string public myString;

    constructor() {
        myString = "Not set yet";
    }

    function setString(string memory _myString) public {
        myString = _myString;
    }

    function getString() public view returns (string memory) {
        return myString;
    }
}

Can anybody help me? Do you need other info? Thanks



Solution 1:[1]

It might be because the account that you are passing to getMessages(String(account)) is not same type as address type in solidity.

In fact, to store the sender's address, you do not have to pass the account as function parameter. Etherum EVM already detect the caller address as msg.sender.

What is msg.sender

IN solidity:

function getMessages() public view returns (string[] memory)
    {
        return messages[msg.sender];
    }

in react side just call the function without passing a parameter.

const handleLoad = () => {
        getMessages()
    }

Solution 2:[2]

I recommend you to see about promises in js, but long history short to see the return value you should wait for it this can be done this way const {state, send} = await useContractFunction(...), but in order to use the await keyword the function where you use it should have the keyword async async (postContract: Contract) =>, remember that all read and writes to the blockchain uses promises

Solution 3:[3]

I've seen that same error when missing await keyword, we should always define it like that so the code waits for the smart contract function execution. I suggest you to add await before calling your smart contract function.

Solution 4:[4]

If you want to get any value, you can not use useContractFunction. Use useCall instead of thats or use ethers library.

Example useCall

import { useCall } from '@usedapp/core';
import { Contract } from '@ethersproject/contracts'; // comes with `@usedapp/core`
import {utils} from 'ethers';

const CONTRACT_ADDR = "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853";
const Interface = new utils.Interface(ABI); // ABI is array
const ContractInstance = new Contract(CONTRACT_ADDR, Interface);

// Custom contract payable function

const useTokenBalance = () => {
  const { value, error } = useCall({
    contract: ContractInstance,
    method: 'TokenBalance',
    args: [] /* params */
  });

  if(error) {
    return undefined
  }
  console.log("value", value);
  return value;
};

function App() {
  //...
  const TokenBalance  = useTokenBalance();

  const getTokenBalance = async () => {
   alert(TokenBalance);
  }
}

API Documentation: https://usedapp-docs.netlify.app/docs/api%20reference/hooks/#usecall

See tests: https://github.com/TrueFiEng/useDApp/blob/17b80218e803395a16a29b94ef0b1b24931f50fb/packages/core/src/hooks/useCall.test.tsx

How to use ethers library in useDApp hook framework;

import { useEthers } from '@usedapp/core';
import { Contract as CTR } from 'ethers';

const CONTRACT_ADDR = "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853";

function App() {
  //...
  const { account, library } = useEthers();

  const getTokenBalance = async () => {
    const signer = library.getSigner();
    const ctr = new CTR(CONTRACT_ADDR, ABI, signer);
    let tx = await ctr.TokenBalance(/* params */);
    alet(tx);
  }
}

API Documentation: https://docs.ethers.io/v5/api/contract/contract/#Contract--creating

Tested versions;

  • ^5.6.5 ethers
  • ^1.0.2 @usedapp/core

Update

Fully source code;

import './App.css';
import { useEthers, useContractFunction, useCall } from '@usedapp/core';
import {useState, useEffect, useCallback } from 'react';
import {utils, constants, BigNumber, Contract as CTR} from 'ethers';
import { Contract } from '@ethersproject/contracts'

const ENDPOINT = "http://127.0.0.1:4000";
const CONTRACT_ADDR = "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9";

const ABI = require('./Post.json').abi;
const Interface = new utils.Interface(ABI);
const Instance = new Contract(CONTRACT_ADDR, Interface);

const useAddMessage = () => {
  const { state, send, event } = useContractFunction(
    Instance,
    'addMessage',
    {}
  );
  return { state, send, event };
};

function App() {
  const { state: addMessageState, send: addMessage } = useAddMessage();

  const { activateBrowserWallet, account, library, chainId } = useEthers();

  const [ownerBalance, set_ownerBalance] = useState(0);
  const [contractInstance, set_contractInstance] = useState(null);

  const [msg, setMsg] = useState("");

  const [chainMsgs, setChainMsgs] = useState([]);

  // Error watching
  useEffect(() => {
    addMessageState.status === 'Exception' &&
      console.log('addMessageState: ', addMessageState.errorMessage);

    addMessageState.status === 'Success' &&
      console.log('Successfully added message to chain');
  }, [addMessageState]);

  useEffect(() => {
    if(account){

      library.getBalance(account).then(balance => {
        set_ownerBalance(utils.formatUnits(balance, "ether"));
      });

      set_contractInstance(new CTR(CONTRACT_ADDR, ABI, library.getSigner()));
    }
  }, [account]);
  
  const addMsg = async () => {
    if(msg.length > 0){
      let addedMessage = await addMessage(msg);
      // when below function correctly worked, that triggered `Error watching` statement
    }
  };

  const getMsg = async () => {
    let chainMessages = await contractInstance.getMessages({from: account});
    setChainMsgs(chainMessages);
  };

  return (
    <>
      <header>
        {
          !account &&
          <button onClick={() => activateBrowserWallet()} >Connect Wallet</button>
        }

        {
          account &&
          <>
            <div style={{display: 'flex'}}>
              <h1 id="msg-addr">{ellipseAddress(account, 5)}</h1>
              <span style={{margin: '0 0.5rem'}}>{ownerBalance} ETH</span>
            </div>
            <br />
          </>
        }
      </header>

      <main>
        {
          account &&
          <>
            <section>
              <input type={"text"} placeholder={"Leave trail"} value={msg} onChange={e => setMsg(e.target.value)} />
              <button onClick={async () => await addMsg()}>Add Message</button>
            </section>
            <section>
              <button onClick={async () => await getMsg()}>Get Message</button>
              {
                chainMsgs.length > 0 &&
                <ul>
                  {chainMsgs.map((chainMsg, i) => {
                    return (
                      <li key={i}>{chainMsg}</li>
                    )
                  })}
                </ul>
              }
              
            </section>
          </>
        }
        
      </main>
    </>
  );
}

function ellipseAddress(address='', width=10) {
  return !address ? '' : address.slice(0, width) + "..."  + address.slice(-width);
}

export default App;

output

Check correctly package version from package.json;

"dependencies": {
    "@testing-library/jest-dom": "^5.14.1",
    "@testing-library/react": "^13.0.0",
    "@testing-library/user-event": "^13.2.1",
    "@usedapp/core": "^1.0.2",
    "ethers": "^5.6.5",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.0"
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yilmaz
Solution 2 jhonny
Solution 3 Mario Saucedo
Solution 4