简体   繁体   中英

How to keep MetaMask connection to the UI persistent with Web3-react?

I am working with web3-react and I cannot figure out how to keep the connection to the MetaMask wallet persistent upon browser refreshes.

This is the code:

// define the injectedConnectors

const injectedConnector = new InjectedConnector({
  supportedChainIds: [
    1, // Mainet
    3, // Ropsten
    4, // Rinkeby
    5, // Goerli
    42, // Kovan
  ],
})

const { chainId, account, activate, active } = useWeb3React()
// activate the wallet
activate(injectedConnector)
console.log(account)
// all good. 

Up to here all is working and I activate my MetaMask wallet as well as I get the account correctly logged, and the active variable is a boolean that changes to true. The problem is that when I refresh the page the active turns to false and I lose the connection between the UI to the MetaMask wallet. Of course saving active into the browser does not change anything because the connection relies on the active boolean value. The docs are lacking such information. Any help would be gladly appreciated.

Finally found a solution! I was trying to use the example in the official library using ... but for some reason it wasn't working though no error came out. Then I stumbled upon some guy who had the same issue and posted on reddit and got a good answer that works for me. This is the link to the post: https://www.reddit.com/r/ethdev/comments/nw7iyv/displaying_connected_wallet_after_browser_refresh/h5uxl88/?context=3 and this is the code from that post:

First create a file that holds the injectedConnector called connectors.js:

import { InjectedConnector } from '@web3-react/injected-connector'
export const Injected = new InjectedConnector({ supportedNetworks: [1, 3, 4, 5, 42] })

Then create a component that checks if the user already activated the wallet:

import React, { useEffect, useState } from 'react'
import { injected } from '../connectors'
import { useWeb3React } from '@web3-react/core'

function MetamaskProvider({ children }) {
  const { active: networkActive, error: networkError, activate: activateNetwork } = useWeb3React()
  const [loaded, setLoaded] = useState(false)
  useEffect(() => {
    injected
      .isAuthorized()
      .then((isAuthorized) => {
        setLoaded(true)
        if (isAuthorized && !networkActive && !networkError) {
          activateNetwork(injected)
        }
      })
      .catch(() => {
        setLoaded(true)
      })
  }, [activateNetwork, networkActive, networkError])
  if (loaded) {
    return children
  }
  return <>Loading</>
}

export default MetamaskProvider

And wrap MetamaskProvider around the components you want the wallet to be activated upon refresh:

 return (
    <ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
      <StylesProvider injectFirst>
        <Paper>
          <Router>
            <Web3ReactProvider getLibrary={getLibrary}>
              <MetamaskProvider>
              {...children components}
              </MetamaskProvider>
            </Web3ReactProvider>
          </Router>
        </Paper>
      </StylesProvider>
    </ThemeProvider>
  );

Its actually really simple. You can just store the connect address in local storage and when the user clicks the disconnect button then remove the address from local storage. basically we use the condition that if there is an acccount in local storage then we connect on load and if not then we have to manually click the connect button. Consider the code below. Note that ideally you should write the logic as a hook and use the hook in the main app then pass in the props the "active" status which is returned from useWeb3React(). but for the purpose of this example i just keep the connect logic in one file to make it read easier

import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import detectEthereumProvider from '@metamask/detect-provider';
import { useWeb3React } from "@web3-react/core"
import { InjectedConnector } from '@web3-react/injected-connector'

//declare supportated chains
export const injected = new InjectedConnector({
  supportedChainIds: [1, 3, 4, 5, 42, 1337, 43114],
})


export default function connButton() {

var web3;
var accounts;
var connected

const [loading, setLoading] = useState(false)

//here we can destructure out various things from web3React such as
//active (which is true if the user is connected and false otherwise)
//activate and deactiveate which we use to instansiate and break the users
//connection
const { active, account, library, connector, activate, deactivate } = useWeb3React()

//set up an elemnt in local storage that we use to hold the connected account
var acc = localStorage.getItem("account")



//function that initialises web3.js
const connectWalletHandler = () => {
    if (window.ethereum && window.ethereum.isMetaMask) {
        console.log('MetaMask Here!');
        web3 = new Web3(window.ethereum);

        window.ethereum.request({ method: 'eth_requestAccounts'})
        
    } else {
        console.log('Need to install MetaMask');
        // setErrorMessage('Please install MetaMask browser extension to interact');
    }
    console.log(web3.eth.currentProvider)
}

//function that is called on page load if and only if their exists and
//item for the user accoun tin local storage
async function connectOnLoad() {

     try {
        
        //here we use activate to create the connection
        await activate(injected)
        connected = true
      } catch (ex) {
        console.log(ex)
      }

      //we use web3.eth to get the accounts to store it in local storage
      var accounts1 = await web3.eth.getAccounts();
      acc = localStorage.setItem("account", accounts1);
}

//here we use a useEffect so that on page load we can check if there is
//an account in local storage. if there is we call the connect onLoad func
//above which allows us to presist the connection and i also call connectWalletHandler
which sets up web3.js so we can call web3.eth.getAccounts()
useEffect(() => {

        
    if (acc != null) {
    connectOnLoad()
    }
    connectWalletHandler()
}, [])


//however in the case where there is no item in local storage we use this
//function to connect which is called when we click the connect button. its
//essentially the same but we check if local storage is null if it is we activate
//if its not then we disconnect. And when we disconnect we remove the acccount from local storage
async function connectOnClick() {
    
    if (localStorage.getItem("account") == null) {

        setLoading(true);
        try {
            await activate(injected)
            connected = true
        } catch (ex) {
            console.log(ex)
        }
        // window.location.reload();
        var accounts1 = await web3.eth.getAccounts();
        console.log(accounts1)
        acc = localStorage.setItem("account", accounts1);
        console.log(acc)
        setTimeout(function(){
            setLoading(false)
         }, 1600);//wait 2 seconds
        
    } else {

        disconnect();
        connected = false
    }

}

async function disconnect() {
    try {
    deactivate()
    localStorage.removeItem("account");
    } catch (ex) {
    console.log(ex)
    }
}



return (
  
    //remember the active boolean from useReactWeb3() stores a bool
    //depending on if the user is or is not connected there for we can 
    //use this as a conditon to render the button saying "Connect Wallet"
    or displaying their address as the text.
    <div>
          {active ? <button onClick={connectOnClick}>{account.substring(0, 6)}...{account.substring(account.length - 4)}</button> : <button onClick={connectOnClick}>Connect Wallet</button>}
    </div>
  );
}

then in your app.js remember to wrap your entire app in the tag. remember this means you need to import web3React into your app.js also

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM