4

I have a relatively basic project using solidity and react working as a single page dApp using Create React App. However I am trying to move this now to Nextjs and have hit a hurdle which I assume is something to do with the server side stuff Nextjs does. I have removed all the redundant code and just provide enough to generate the error:

import { ethers, Contract } from 'ethers';
import Project from '../src/artifacts/contracts/Project.sol/Project.json';

const contractAddress = process.env.contract_address;

export default function App() {

  const provider = new ethers.providers.Web3Provider(window.ethereum);      

  console.log(provider.getSigner())

  return (
    <div className="App">
      <p>Hello!</p>
    </div>
  );

}

This errors with:

window is not defined

I saw someone else suggest loading and setting it via state like so:

const [provider, setProvider] = useState({})

React.useEffect(() => {
   setProvider(new ethers.providers.Web3Provider(window.ethereum))
}, []);

const signer = provider.getSigner(); 

But this returns: TypeError: provider.getSigner is not a function

However if i comment out this code, refresh and let the page load, then uncomment the code and let hot reload refresh the component I get no such error and can successfully console.log the signer.

Pulling my limited supply of hair out trying to resolve this, any help would be appreciated.

Zeb
  • 547
  • 4
  • 15
  • 1
    Why would you App need `window` at all? You're already importing `ethers`, surely that has a list of Web3Provider identifiers that you can pass. Also, if something needs to happen "as a side effect", use `useEffect` (but remember that by default it triggers [after every single render](https://reactjs.org/docs/hooks-reference.html#useeffect). This is not a side effect: it's crucial to your app's functioning, so this state, and `useState` is the correct function, where you bind your provider as the [initial value](https://reactjs.org/docs/hooks-reference.html#usestate). – Mike 'Pomax' Kamermans Aug 01 '21 at 14:15
  • @Mike'Pomax'Kamermans I'm pretty early into my education of both solidity and react but this is how I have learnt to declare the provider via the Ethers documentation: https://docs.ethers.io/v5/migration/web3/ – Zeb Aug 01 '21 at 14:28
  • That's documentation for only if you're migrating from preexisting web3.js code, so you didn't learn how to use Ethers from that: that's niche documentation for people working on refactors/dependency uplifts only. Are you converting a pre-existing web3.js project or are you writing new code? – Mike 'Pomax' Kamermans Aug 01 '21 at 14:33
  • Does this answer your question? [Window is not defined in Next.js React app](https://stackoverflow.com/questions/55151041/window-is-not-defined-in-next-js-react-app) – juliomalves Aug 01 '21 at 14:39
  • @Mike'Pomax'Kamermans sorry I linked to the incorrect section, the same method is suggested in the 'getting started' section of the docs: https://docs.ethers.io/v5/getting-started/ – Zeb Aug 01 '21 at 14:48
  • @juliomalves just spotted it myself and having a read through, thanks. – Zeb Aug 01 '21 at 14:49
  • You have a few options, but the easiest on by far is to not use a functional component here, but a class component, so you have a dedicated `componentDidMount` function that you can work with (as per the above-linked post) – Mike 'Pomax' Kamermans Aug 01 '21 at 14:59
  • OK, thanks. I'll try refactoring and see how it goes (I was under the impression that useEffect was a suitable replacement for componentDidMount in functional components). – Zeb Aug 01 '21 at 15:22
  • Same error with a class component. Its not `window` that it cannot find at this stage, it is a method of the provider object (like `provider.getSigner()`) or `provider.listAccounts()`), and still only on the first time the page loads – Zeb Aug 01 '21 at 16:19

2 Answers2

6

I have managed to get this working whilst sticking with my functional components.

Within useEffect I included a statement to check if the windowobject was undefined:

if (typeof window.ethereum !== "undefined" || (typeof window.web3 !== "undefined")) {
   // Existing code goes here
}

And then had to make sure that any variables that I wanted to use outside of this if statement were saved to state as well as declared within the statement. Like:

const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider)

This seemed to solve most of the issues with moving from CRA to Next and I now have the dApp back up and running.

Zeb
  • 547
  • 4
  • 15
0
  const [signer, setSigner] = useState(undefined);

  useEffect(() => {
    if (typeof window.ethereum !== "undefined") {
      console.log("MetaMask is installed!");
      const provider = new ethers.providers.Web3Provider(
        window.ethereum as any,
      );
      console.log(provider);
      setSigner(provider.getSigner());
    }
  }, []);
  • Please explain why and how this fixes the issue – Krismu Apr 25 '23 at 09:01
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Krismu Apr 25 '23 at 09:02