Connect Metamask wallet to a React App

Photo by PiggyBank on Unsplash

Connect Metamask wallet to a React App

Β·

10 min read

Introduction

I got referred by someone to create a frontend react app for a web3 project. I have never developed anything in the web3 space but I liked the challenge so I took it up.

My task was to connect MetaMask and Solana Phantom wallets to a React app.

I had to do a lot of research to get this working and I will be dropping links to resources that were helpful at the end of this tutorial.

Side note: This article was supposed to contain MetaMask and Phantom wallet connection to a React app. However, after writing about MetaMask connection I realized the article was getting too long so I decided to break it up and write about Phantom wallet connection in another article.

What we are building

We will be building a simple button that prompts a modal with MetaMask and Phantom wallet options so users can select which wallet to connect to.

The user's wallet address is also going to be displayed on the Navbar after they connect.

Let's get started!

Getting started

To get started, we have to create a react app called web3-wallet

I'm assuming you are already familiar with React setup so I won't go into that. If you aren't you can read about setting up your react app here

We delete everything in src except index.js, app.js, and index.css

Now, let's build out a basic React app that the wallets will connect to.

Creating a basic React app

We will create a navbar with a connect button for users to click on.

I'll be using tailwindCSS for the styling because I love it 😍

Tailwind setup

To install tailwindCSS, run the following command on your terminal

yarn add -D tailwindcss postcss autoprefixer

Create a tailwind.config.js file on your root folder and paste this

module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

Also, create postcss.config.js file on your root folder and paste this

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

Lastly, add the following to your index.css file

@tailwind base;
@tailwind components;
@tailwind utilities;

Navbar component

In src, create a folder called components that will host all the components used on this app.

In the components folder, create a component called Navbar.js The Navbar will have a logo, and a button and also display the wallet address when a user connects to a wallet.

import React from 'react';

const Navbar = () => {
  return (
    <nav className='lg:px-52 px-6 py-8 bg-[#8b5cf6] relative'>
      <div className='flex justify-between transition-all duration-500 
         ease-in rounded'>
        <div className='text-white font-bold text-3xl'>LOGO</div>
        <h3 className='text-white font-semibold mr-4'>Wallet address:</h3>
        <button
          type='button'
          className='my-6 lg:my-0 bg-white text-primary-200 font-semibold 
          py-2 px-6 items-center rounded-lg flex justify-center >
          Connect
        </button>
      </div>
    </nav>
  );
};

export default Navbar;

In App.js, let's clear the boilerplate code and import Navbar

import React from 'react';
import Navbar from './components/Navbar';

const App = () => {
  return (
    <>
      <Navbar />
    </>
  );
};

export default App;

On the terminal run yarn start to open the app on http://localhost:3000/

We can see our Navbar with the connect button on it.

navbar.png

We want the button to open up a modal and we'll use useState hook to keep track of the state of the modal.

Since this state is going to be accessed by more than one component, it is best practice to keep it in a common parent component, which in this case is App.js

In App.js, import useState and create a state called modalOpen setting the initial value to false.

To open the Modal, modalOpen needs to be true, so let's pass this as a props in the Navbar component.

import React, { useState } from 'react';
import Navbar from './components/Navbar';

const App = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <>
      <Navbar openModal={() => setModalOpen(true)} />
    </>
  );
};

export default App;

In the Navbar.js file, import openModal and pass it to the onClick event of the connect button.

const Navbar = ({ openModal }) => {
  return (
    <nav className='lg:px-52 px-6 py-8 bg-[#8b5cf6] relative'>
      <div className='flex justify-between transition-all duration-500 
         ease-in rounded'>
        <div className='text-white font-bold text-3xl'>LOGO</div>

        <h3 className='text-white font-semibold mr-4'>Wallet address:</h3>
        <button
          type='button'
          className='my-6 lg:my-0 bg-white text-primary-200 font-semibold py-2 
            px-6 items-center rounded-lg flex justify-center '
          onClick={openModal}
        >
          Connect
        </button>
      </div>
    </nav>
  );
};

export default Navbar;

To check if this works, add console.log(modalOpen) to the App.js file.

When we inspect the app, we can see false logged on the console and when the connect button is clicked true gets logged on the console. So it works!

Now, we can build the Modal component.

Modal component

In the components folder, create a file called Modal.js.

We also need to pass the modalOpen state to the Modal component. Let's import this component to App.js so that we can pass the state as props.

 return (
    <>
      <Navbar openModal={() => setModalOpen(true)} />
      <Modal isOpen = {modalOpen}/>
    </>
  );

In Modal.js, import the isOpen props

Remember modalOpen needs to be true to render the Modal component, so let's write a condition to this effect.

import React from 'react';

const Modal = ({ isOpen }) => {
  return <>{isOpen ? <h3>I'm open</h3> : null}</>;
};

export default Modal;

This condition renders the Modal only when the state is true, and the state becomes true when the connect button is clicked.

We can see I'm open text displayed on the screen when the connect button is clicked

To close the Modal, we have to set modalOpen to false.

Back in App.js, set modalOpen to false and pass it as props to the Modal component.

     <Modal isOpen={modalOpen} onClose={()=>setModalOpen(false)} />

Let's import onClose in the Modal component and style the component.

import React from 'react';

const Modal = ({ isOpen, onClose }) => {
  return (
    <>
      {isOpen ? (
        <div className='bg-[rgba(0,0,0,0.5)] top-0 left-0 w-full h-screen absolute'>
          <div className=' right-0 left-0 z-50 lg:w-4/12 absolute bg-white p-4 top-20 
             h-full mx-auto'>
            <div className=' py-4 flex px-6 justify-end'>
              <button
                className='border border-current rounded px-4 py-1 text-xl font-semibold 
                cursor-pointer'
                onClick={onClose}
              >
                Close
              </button>
            </div>
            <h3 className='text-center text-2xl font-semibold'>
              Choose Wallet to continue
            </h3>
          </div>
        </div>
      ) : null}
    </>
  );
};

export default Modal;

Now, we have a toggle between the connect button on the Navbar and the close button on the Modal.

Wallets component

Let's create a Wallets component to hold wallet details for MetaMask and Phantom.

In the Wallets.js file, create an array of objects. This array can also be saved in another file and imported here if you like.

  const walletList = [
    {
      name: 'Phantom',
      icon: <img src={PhantomLogo} alt='phantom logo' className='w-[30px]' />,
      button: (
        <button className='bg-[#512da8] hover:bg-[#1a1f2e] text-white 
         py-3 px-5 rounded-[4px]'>
          Connect
        </button>
      ),
    },
    {
      name: 'MetaMask',
      icon: <Icon icon='logos:metamask-icon' className='text-2xl' />,
      button: (
        <button className='bg-[#512da8] hover:bg-[#1a1f2e] text-white 
           py-3 px-5 rounded-[4px]'>
          Connect
        </button>
      ),
    },
  ];

For the icons, I downloaded an image for Phantom wallet and saved it in the assets folder inside src. For MetaMask, I used a MetaMask icon from iconify. You can install iconify by running yarn add -D @iconify/react

We will map through walletList array to render wallet details.

{walletList.map((wallet) => (
          <div
            key={wallet.name}
            className='flex items-center justify-between mb-5 px-4'
          >
            <div className='flex'>
              <span className='mr-3'>{wallet.icon}</span>
              <p> {wallet.name}</p>
            </div>
            <div> {wallet.button}</div>
          </div>
        ))}

Now, we can import Wallets component inside the Modal component

import React from 'react';
import Wallets from './Wallets';

const Modal = ({ isOpen, onClose }) => {
  return (
    <>
      {isOpen ? (
        <div className='bg-[rgba(0,0,0,0.5)] top-0 left-0 w-full h-screen absolute'>
          <div className=' right-0 left-0 z-50 lg:w-4/12 absolute bg-white p-4 
             top-20 h-full mx-auto'>
            <div className=' py-4 flex px-6 justify-end'>
              <button
                className='border border-current rounded px-4 py-1 text-xl 
                 font-semibold cursor-pointer'
                onClick={onClose}
              >
                Close
              </button>
            </div>
            <h3 className='text-center text-2xl font-semibold'>
              Choose Wallet to continue
            </h3>
            <Wallets />
          </div>
        </div>
      ) : null}
    </>
  );
};

export default Modal;

We have built a basic React app and can now connect MetaMask and Phantom wallets to it.

Connecting MetaMask

I found MetaMask pretty straightforward and easy to connect, so I'll start with it.

First off, we add MetaMask extension to our browser.

If you are using chrome browser, click here to add MetaMask to browser, then create a MetaMask account if you don't already have one.

Now we install some dependencies to aid with the connectivity. I'll attach resources to explain what these dependencies do.

yarn add web3 @web3-react/core @web3-react/injected-connector

In App.js we import Web3 and Web3ReactProvider from the dependencies we installed.

We create getLibrary function and pass the function as props to the Web3ReactProvider, then we wrap our components in App.js with the Web3ReactProvider.

import React, { useState } from 'react';
import Modal from './components/Modal';
import Navbar from './components/Navbar';
import Web3 from 'web3';
import { Web3ReactProvider } from '@web3-react/core';

const App = () => {
  const [modalOpen, setModalOpen] = useState(false);
  const getLibrary = (provider) => {
    return new Web3(provider);
  };

  return (
    <>
      <Web3ReactProvider getLibrary={getLibrary}>
        <Navbar openModal={() => setModalOpen(true)} />
        <Modal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
      </Web3ReactProvider>
    </>
  );
};

export default App;

Create a folder called helper in src and a file called helper.js in the folder.

In helper.js, import InjectedConnector from @web3-react/injected-connector

import { InjectedConnector } from '@web3-react/injected-connector';

export const injected = new InjectedConnector({
  supportedChainIds: [1, 3, 4, 5, 42],
});

When a user is connected to MetaMask, we want to be able to get the active status, account information, and some other details and we want to use these details in several components on the app. To avoid props drilling, let's use Context API to pass these props around.

Create a folder in src called context, Let's call the context MetaMaskWalletContext.js

Here we can import useWeb3React and gain access to MetaMask wallet details

import React, { createContext } from 'react';
import { useWeb3React } from '@web3-react/core';

export const MetaMaskWalletContext = createContext();

export const MetaMaskWalletProvider = ({ children }) => {
  const { active, activate, deactivate, account } = useWeb3React();

  return (
    <MetaMaskWalletContext.Provider
      value={{ active, activate, deactivate, account }}
    >
      {children}
    </MetaMaskWalletContext.Provider>
  );
};

export default MetaMaskWalletProvider;

If we need to extract more details from useWeb3React we can just add them later on.

In App.js, let's import MetaMaskWalletProvider and wrap our components with it.

import React, { useState } from 'react';
import Modal from './components/Modal';
import Navbar from './components/Navbar';
import Web3 from 'web3';
import { Web3ReactProvider } from '@web3-react/core';
import { MetaMaskWalletProvider } from './context/MetaMaskWalletContext';

const App = () => {
  const [modalOpen, setModalOpen] = useState(false);
  const getWeb3Library = (provider) => {
    return new Web3(provider);
  };

  return (
    <>
      <Web3ReactProvider getWeb3Library={getWeb3Library}>
        <MetaMaskWalletProvider>
          <Navbar openModal={() => setModalOpen(true)} />
          <Modal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
        </MetaMaskWalletProvider>
      </Web3ReactProvider>
    </>
  );
};

export default App;

Back in Wallets.js, we can use MetaMaskWalletContext and access the details we require.

Let's write a function for the connect button onClick event.

We import injected from the helper file and pass it into activate

import { MetaMaskWalletContext } from '../context/MetaMaskWalletContext';
import { injected } from '../helper/helper';

const { active, activate } = useContext(MetaMaskWalletContext);

 async function connect() {
    try {
      await activate(injected);
    } catch (ex) {
      console.log(ex);
    }
  }

  button: (
        <button
          className='bg-[#512da8] hover:bg-[#1a1f2e] text-white 
           py-3 px-5 rounded-[4px]'
          onClick={connect}
        >
          Connect
        </button>

Now when we click on connect, MetaMask opens up on the browser and asks for password to login. Cool!

Let's make the modal close when a user is connected to MetaMask.

We already wrote an onClose function for the modal close button so we can just pass it down as props to the Wallets component.

   <Wallets onClose={onClose} />

In Wallets.js, let's write a logic to close the modal when the connection is active. We check if a connection is active and trigger onClose

  if (active) {
    onClose();
  }

The next thing we need to do is to display the Wallet address on the navbar.

In Navbar.js, import the MetaMaskWalletContext so we can access account and active status

import { MetaMaskWalletContext } from '../context/MetaMaskWalletContext';

const { account, active } = useContext(MetaMaskWalletContext);

 <h3 className='text-white font-semibold mr-4'>
          {active ? `Wallet address: ${account}` : ''}
        </h3>

Now when we connect, we can see the wallet address clearly on the dashboard.

Let's also change the connect button to disconnect and also write a function to disconnect a user from MetaMask.

In Navbar.js

import { injected } from '../helper/helper';

 async function disconnect() {
    try {
      await deactivate(injected);
    } catch (ex) {
      console.log(ex);
    }
  }
  const handleConnection = () => {
    if (active) {
      disconnect();
    } else {
       openModal();
    }
  };

 <button
          type='button'
          className='my-6 lg:my-0 bg-white text-primary-200 font-semibold py-2 
           px-6 items-center rounded-lg flex justify-center '
          onClick={handleConnection}
        >
          {active ? 'Disconnect' : 'Connect'}
        </button>

Tada! there we have itπŸ˜†

We can connect to MetaMask wallet, display the wallet address, and disconnect when we want.

I hope this article has been helpful. Tell me what you think by dropping a comment.πŸ˜ƒ

Github repo : https://github.com/Netacci/metamask-connect

Metamask resources : https://www.youtube.com/watch?v=DCA53Go5ON8&t=622s&ab_channel=ShmojiCodes

Β