Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HoC to hooks #303

Open
fernandomg opened this issue Sep 16, 2024 · 0 comments
Open

HoC to hooks #303

fernandomg opened this issue Sep 16, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@fernandomg
Copy link
Member

I was going through the codebase and thought we might change the HoCs to hooks. I believe the suspense wrappers need to stay as HoC/wrappers, as their usage may require a lot of boilerplate. But in the case of the walletStatusVerifier, this is what I got after a long debate (????) with ChatGPT.

Note

There are ELI5 parts, but I left them as I like how everything is justified and detailed.


Overview

Goal: Replace the HoC and the WalletStatusVerifier component with a custom hook.
Benefit: Simplify the code, make it more readable, and embrace the React Hooks pattern.
Approach: Create a useWalletStatusVerifier hook that encapsulates the logic previously handled by the HoC and the component.


Step-by-Step Refactoring

1. Analyze the Existing Components

WalletStatusVerifier Component:
- Purpose: Checks the wallet connection and synchronization status.
- Behavior:
- If the wallet is not connected, render a fallback component (default: ConnectWalletButton).
- If the wallet is connected but not synced with the correct chain, render a button to switch the chain.
- If the wallet is connected and synced, render the children components.
withWalletStatusVerifier HoC:
- Purpose: Wraps a component to provide the same wallet status verification.
- Behavior: Similar to WalletStatusVerifier but wraps a component instead of using children.

2. Create a Custom Hook

We'll create a useWalletStatusVerifier hook that:

  • Accepts: chainId, fallback, and labelSwitchChain (optional).
  • Returns:
    • isReady: A boolean indicating if the wallet is connected and synced.
    • statusElement: A React element to render when the wallet is not ready (fallback or switch chain button).

Custom Hook Implementation:

import { ReactElement } from 'react'
import { extractChain } from 'viem'

import { Button } from '@/src/components/sharedComponents/ui/Buttons'
import { useWeb3Status } from '@/src/hooks/useWeb3Status'
import { chains, ChainsIds } from '@/src/lib/networks.config'
import { ConnectWalletButton } from '@/src/providers/Web3Provider'

interface WalletStatusVerifierProps {
  chainId?: ChainsIds
  fallback?: ReactElement
  labelSwitchChain?: string
}

export const useWalletStatusVerifier = ({
  chainId,
  fallback = <ConnectWalletButton />,
  labelSwitchChain = 'Switch to',
}: WalletStatusVerifierProps = {}) => {
  const {
    appChainId,
    isWalletConnected,
    isWalletSynced,
    switchChain,
    walletChainId,
  } = useWeb3Status()

  const chainToSwitch = extractChain({
    chains,
    id: chainId || appChainId || chains[0].id,
  })

  if (!isWalletConnected) {
    return { isReady: false, statusElement: fallback }
  }

  if (!isWalletSynced || walletChainId !== chainToSwitch.id) {
    return {
      isReady: false,
      statusElement: (
        <Button onClick={() => switchChain(chainToSwitch.id)}>
          {labelSwitchChain} {chainToSwitch?.name}
        </Button>
      ),
    }
  }

  return { isReady: true, statusElement: null }
}

Explanation:
Inputs:

  • chainId: The desired chain ID.
  • fallback: Component to render if the wallet is not connected.
  • labelSwitchChain: Label for the switch chain button.
    Outputs:
  • isReady: Indicates whether the wallet is connected and synced to the correct chain.
  • statusElement: The element to render if the wallet is not ready.

3. Update Components to Use the Custom Hook

Example Usage in a Component:

import { FC } from 'react'
import { useWalletStatusVerifier } from '@/src/hooks/useWalletStatusVerifier'

const MyComponent: FC = () => {
  const { isReady, statusElement } = useWalletStatusVerifier()

  if (!isReady) {
    return statusElement
  }

  // The wallet is connected and synced; render the component's content.
  return (
    <div>
      {/* Your component logic goes here */}
      <p>The wallet is connected and synced!</p>
    </div>
  )
}

Explanation:

  • Use the useWalletStatusVerifier hook at the top of your component.
  • Check the isReady flag:
    • If false, return the statusElement (either the fallback or the switch chain button).
    • If true, render your component's content.

Benefits:

  • Simplifies the component logic.
  • Avoids nesting components or using HoCs.
  • Makes the code more readable and maintainable.
  1. Remove the HoC and Component
    Since we have replaced the functionality with a custom hook, you can remove the withWalletStatusVerifier HoC and the WalletStatusVerifier component from your codebase.

Deleted Code:

// Remove the following from your code:

const WalletStatusVerifier: FC<WalletStatusVerifierProps> = ({ ... }) => { ... }

const withWalletStatusVerifier = <P extends object>(WrappedComponent: ComponentType<P>, ...) => { ... }

Final Code

Custom Hook (useWalletStatusVerifier.ts):

import { ReactElement } from 'react'
import { extractChain } from 'viem'

import { Button } from '@/src/components/sharedComponents/ui/Buttons'
import { useWeb3Status } from '@/src/hooks/useWeb3Status'
import { chains, ChainsIds } from '@/src/lib/networks.config'
import { ConnectWalletButton } from '@/src/providers/Web3Provider'

interface WalletStatusVerifierProps {
  chainId?: ChainsIds
  fallback?: ReactElement
  labelSwitchChain?: string
}

export const useWalletStatusVerifier = ({
  chainId,
  fallback = <ConnectWalletButton />,
  labelSwitchChain = 'Switch to',
}: WalletStatusVerifierProps = {}) => {
  const {
    appChainId,
    isWalletConnected,
    isWalletSynced,
    switchChain,
    walletChainId,
  } = useWeb3Status()

  const chainToSwitch = extractChain({
    chains,
    id: chainId || appChainId || chains[0].id,
  })

  if (!isWalletConnected) {
    return { isReady: false, statusElement: fallback }
  }

  if (!isWalletSynced || walletChainId !== chainToSwitch.id) {
    return {
      isReady: false,
      statusElement: (
        <Button onClick={() => switchChain(chainToSwitch.id)}>
          {labelSwitchChain} {chainToSwitch?.name}
        </Button>
      ),
    }
  }

  return { isReady: true, statusElement: null }
}

Component Usage Example (MyComponent.tsx):

import { FC } from 'react'
import { useWalletStatusVerifier } from '@/src/hooks/useWalletStatusVerifier'

const MyComponent: FC = () => {
  const { isReady, statusElement } = useWalletStatusVerifier({
    chainId: 1, // Optional: specify the chain ID
    fallback: <div>Please connect your wallet</div>, // Optional: custom fallback
    labelSwitchChain: 'Switch Network', // Optional: custom label
  })

  if (!isReady) {
    return statusElement
  }

  return (
    <div>
      {/* Your component logic */}
      <p>Wallet is connected and on the correct network.</p>
    </div>
  )
}

Explanation and Benefits

Why Use a Custom Hook?

  • Encapsulation of Logic: The hook encapsulates all the logic related to wallet status verification.
  • Reusability: You can use the hook across multiple components without repeating code.
  • Simplicity: Hooks are more straightforward and align with modern React practices compared to HoCs.
  • Flexibility: Components using the hook can have their own rendering logic without being wrapped.

Advantages Over HoCs

  • Avoids Component Nesting: HoCs can lead to deeply nested component trees, making debugging harder.
  • Better TypeScript Support: Type inference and type-checking are often simpler with hooks.
  • Easier to Understand: Hooks are functions that return values, which is a familiar pattern.

Additional Customization

You can further customize the hook or its usage:

  1. Customizing the Fallback Component
    When calling the hook, you can pass a custom fallback component:
const { isReady, statusElement } = useWalletStatusVerifier({
  fallback: <MyCustomConnectWalletButton />,
})
  1. Specifying a Different Chain ID
    If you need to enforce a specific chain ID:
const { isReady, statusElement } = useWalletStatusVerifier({
  chainId: 137, // For Polygon Mainnet, for example
})
  1. Changing the Switch Chain Button Label
    Customize the label:
const { isReady, statusElement } = useWalletStatusVerifier({
  labelSwitchChain: 'Please switch to',
})

Handling Edge Cases

If you need to perform additional actions when the wallet becomes ready, you can use useEffect:

import { useEffect } from 'react'

const MyComponent: FC = () => {
  const { isReady, statusElement } = useWalletStatusVerifier()

  useEffect(() => {
    if (isReady) {
      // Perform any additional setup or data fetching
    }
  }, [isReady])

  if (!isReady) {
    return statusElement
  }

  return (
    <div>
      {/* Component content */}
    </div>
  )
}

Conclusion

By refactoring your HoC and component into a custom hook, you achieve the following:

  • Modernization: Align with the current best practices in React development.
  • Simplification: Reduce complexity by eliminating the need for HoCs.
  • Flexibility: Allow components to manage their own rendering logic based on the wallet status.

Summary

  • Refactored the HoC and component into a useWalletStatusVerifier hook.
  • Simplified component usage by leveraging the hook's returned values.
  • Maintained the same functionality with cleaner, more modern code.
@fernandomg fernandomg added the enhancement New feature or request label Sep 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant