-
Notifications
You must be signed in to change notification settings - Fork 3
Description
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, andlabelSwitchChain(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
useWalletStatusVerifierhook at the top of your component. - Check the
isReadyflag:- If
false, return thestatusElement(either the fallback or the switch chain button). - If
true, render your component's content.
- If
Benefits:
- Simplifies the component logic.
- Avoids nesting components or using HoCs.
- Makes the code more readable and maintainable.
- Remove the HoC and Component
Since we have replaced the functionality with a custom hook, you can remove thewithWalletStatusVerifierHoC and theWalletStatusVerifiercomponent 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:
- Customizing the Fallback Component
When calling the hook, you can pass a custom fallback component:
const { isReady, statusElement } = useWalletStatusVerifier({
fallback: <MyCustomConnectWalletButton />,
})- 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
})- 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
useWalletStatusVerifierhook. - Simplified component usage by leveraging the hook's returned values.
- Maintained the same functionality with cleaner, more modern code.