From 0a9d7346b8e71a44785a03a68e6231c077ca51f7 Mon Sep 17 00:00:00 2001 From: Dan Oved Date: Tue, 30 Jan 2024 09:48:59 -0800 Subject: [PATCH] switch to use router context --- src/app/App.tsx | 7 ++-- src/app/CreateSafe.tsx | 14 +++---- src/app/NewSafeProposal.tsx | 55 +++++--------------------- src/app/Root.tsx | 49 ++++++++++++++++------- src/app/SafeInformationPage.tsx | 2 +- src/app/ViewSafe.tsx | 59 ++++++++++------------------ src/app/Wrapper.tsx | 4 +- src/components/AddressView.tsx | 6 +-- src/components/Contexts.ts | 23 +++++++++++ src/components/DataActionPreview.tsx | 7 ++-- src/components/NetworkSwitcher.tsx | 41 ++++++------------- src/components/SafeInformation.tsx | 8 ++-- src/components/SetOwnerModal.tsx | 10 ++--- 13 files changed, 130 insertions(+), 155 deletions(-) create mode 100644 src/components/Contexts.ts diff --git a/src/app/App.tsx b/src/app/App.tsx index d9d2100..efec043 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,14 +1,13 @@ import { FormControl, Button, TextField, Divider, View, Card } from "reshaped"; import { useFormik } from "formik"; import { isAddress } from "viem"; -import { useNavigate } from "react-router-dom"; -import { useContext } from "react"; -import { CurrentNetwork } from "./Root"; +import { useNavigate, useOutletContext } from "react-router-dom"; +import { NetworkContext } from "../components/Contexts"; function FormAddressInput() { const navigate = useNavigate(); - const network = useContext(CurrentNetwork); + const { currentNetwork: network } = useOutletContext(); const formik = useFormik({ initialValues: { diff --git a/src/app/CreateSafe.tsx b/src/app/CreateSafe.tsx index 103d222..a90f9b0 100644 --- a/src/app/CreateSafe.tsx +++ b/src/app/CreateSafe.tsx @@ -1,14 +1,14 @@ -import { useCallback, useContext, useEffect, useState } from "react"; -import { CurrentNetwork, WalletProviderContext } from "./Root"; +import { useCallback, useEffect, useState } from "react"; import { Field, FieldArray, Formik } from "formik"; import { Text, Button, FormControl, TextField, View, useToast } from "reshaped"; import { allowedNetworks, contractNetworks } from "../chains"; import { isAddress } from "viem"; import { ethers } from "ethers"; import { EthersAdapter, SafeFactory } from "@safe-global/protocol-kit"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useOutletContext } from "react-router-dom"; import { AbstractSigner } from "ethers"; import { BrowserProvider } from "ethers"; +import { NetworkContext } from "../components/Contexts"; function validateAddress(value: string) { if (!isAddress(value)) { @@ -30,8 +30,8 @@ function validateSafeArguments(values: any) { } export function CreateSafe() { - const provider = useContext(WalletProviderContext); - const network = useContext(CurrentNetwork); + const { walletProvider: provider } = useOutletContext(); + const { currentNetwork: network } = useOutletContext(); const toaster = useToast(); const navigate = useNavigate(); const [signerInfo, setSignerInfo] = useState< @@ -51,7 +51,7 @@ export function CreateSafe() { if (provider) { updateSignerInfo(provider); } - }, [provider]); + }, [provider, updateSignerInfo]); const submitCallback = useCallback( async (data: any) => { @@ -86,7 +86,7 @@ export function CreateSafe() { }); } }, - [provider, signerInfo], + [navigate, network, signerInfo, toaster] ); return ( diff --git a/src/app/NewSafeProposal.tsx b/src/app/NewSafeProposal.tsx index 3199f18..7f087ff 100644 --- a/src/app/NewSafeProposal.tsx +++ b/src/app/NewSafeProposal.tsx @@ -1,22 +1,14 @@ import { Field, FieldArray, Formik } from "formik"; import { SafeInformation } from "../components/SafeInformation"; import { Card, View, Text, Button, useToast } from "reshaped"; -import { - SyntheticEvent, - useCallback, - useContext, - useEffect, - useState, -} from "react"; +import { SyntheticEvent, useCallback, useEffect, useState } from "react"; import { Address, Hex, formatEther } from "viem"; import { validateAddress, validateETH } from "../utils/validators"; import { GenericField } from "../components/GenericField"; import { DataActionPreview } from "../components/DataActionPreview"; import Safe, { EthersAdapter } from "@safe-global/protocol-kit"; -import { WalletProviderContext } from "./Root"; import { ethers } from "ethers"; import { contractNetworks } from "../chains"; -import { SafeInformationContext } from "./ViewSafe"; import { DEFAULT_ACTION_ITEM, DEFAULT_PROPOSAL, @@ -30,6 +22,8 @@ import { transformValuesToWei, } from "../utils/etherFormatting"; import { BrowserProvider } from "ethers"; +import { useOutletContext } from "react-router-dom"; +import { NetworkContext, SafeContext } from "../components/Contexts"; const FormActionItem = ({ name, @@ -140,36 +134,11 @@ const signAndExecuteTx = async ({ return executedTxn; }; -const useSafe = ({ - provider, - safeAddress, -}: { - provider: BrowserProvider | null; - safeAddress: Address | undefined; -}) => { - const [safe, setSafe] = useState(); - - useEffect(() => { - if (!provider || !safeAddress) { - return; - } - - const loadSafe = async () => { - const adapter = await createSafeAdapter({ provider, safeAddress }); - setSafe(adapter); - }; - - loadSafe(); - }, [provider, safeAddress]); - - return safe; -}; - const useGetSafeTxApprovals = ({ proposal }: { proposal: Proposal }) => { - const safeInformation = useContext(SafeInformationContext); + const { safeInformation } = useOutletContext(); - const safeSdk = safeInformation?.safeSdk; - const safeSdk2 = safeInformation?.safeSdk2; + const safeSdk = safeInformation.safeSdk; + const safeSdk2 = safeInformation.safeSdk2; const [approvers, setApprovers] = useState([]); @@ -196,7 +165,7 @@ const useGetSafeTxApprovals = ({ proposal }: { proposal: Proposal }) => { }; const useAccountAddress = () => { - const walletProvider = useContext(WalletProviderContext); + const { walletProvider } = useOutletContext(); const [address, setAddress] = useState
(); @@ -244,12 +213,8 @@ const ViewProposal = ({ proposal: Proposal; handleEditClicked: (evt: SyntheticEvent) => void; }) => { - const safeInformation = useContext(SafeInformationContext); - const walletProvider = useContext(WalletProviderContext); - const safe = useSafe({ - provider: walletProvider, - safeAddress: safeInformation?.address, - }); + const { safeInformation } = useOutletContext(); + const safe = safeInformation.safeSdk; const toaster = useToast(); @@ -380,7 +345,7 @@ const EditProposal = ({ } setIsEditing(false); }, - [setIsEditing, setProposal], + [proposal, setIsEditing, setProposal, setProposalParams] ); const defaultActions = proposal || DEFAULT_PROPOSAL; diff --git a/src/app/Root.tsx b/src/app/Root.tsx index e209355..cbcab0a 100644 --- a/src/app/Root.tsx +++ b/src/app/Root.tsx @@ -1,18 +1,17 @@ import { ethers } from "ethers"; -import { createContext, useCallback, useEffect, useState } from "react"; -import { Outlet } from "react-router-dom"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Outlet, useParams } from "react-router-dom"; import { Button, View } from "reshaped"; import { NetworkSwitcher } from "../components/NetworkSwitcher"; import { BrowserProvider } from "ethers"; - -export const WalletProviderContext = - createContext(null); -export const CurrentNetwork = createContext(0); +import { NetworkContext } from "../components/Contexts"; export const Root = () => { const [provider, setProvider] = useState< ethers.BrowserProvider | undefined >(); + const networkIdFromRoute = useParams().networkId; + const [currentNetwork, setCurrentNetwork] = useState(0); const connectMetamask = useCallback(async () => { @@ -43,7 +42,29 @@ export const Root = () => { connectMetamask(); }, [connectMetamask]); - if (!provider) { + const networkContext: NetworkContext | undefined = useMemo(() => { + if (!provider) return; + + return { + walletProvider: provider, + currentNetwork, + }; + }, [provider, currentNetwork]); + + useEffect(() => { + if (!networkIdFromRoute) return; + if (currentNetwork !== Number(networkIdFromRoute)) { + provider?.send("wallet_switchEthereumChain", [ + { + chainId: `0x${parseInt(networkIdFromRoute).toString(16)}`, + }, + ]); + + setCurrentNetwork(Number(networkIdFromRoute)); + } + }, [currentNetwork, networkIdFromRoute, setCurrentNetwork, provider]); + + if (!networkContext) { return ( @@ -52,13 +73,11 @@ export const Root = () => { } return ( - - -
-
- - -
-
+ <> +
+
+ + + ); }; diff --git a/src/app/SafeInformationPage.tsx b/src/app/SafeInformationPage.tsx index 7f36e93..b18b6bb 100644 --- a/src/app/SafeInformationPage.tsx +++ b/src/app/SafeInformationPage.tsx @@ -1,6 +1,6 @@ import { Button, View } from "reshaped"; import { SafeInformation } from "../components/SafeInformation"; -import { Link, useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; export const SafeInformationPage = () => { const { networkId, safeAddress } = useParams(); diff --git a/src/app/ViewSafe.tsx b/src/app/ViewSafe.tsx index 673afeb..9f1e9da 100644 --- a/src/app/ViewSafe.tsx +++ b/src/app/ViewSafe.tsx @@ -1,17 +1,11 @@ -import { - useState, - createContext, - useContext, - useEffect, - useCallback, -} from "react"; -import { Outlet, useParams } from "react-router-dom"; -import { WalletProviderContext } from "./Root"; +import { useState, useEffect, useCallback, useMemo } from "react"; +import { Outlet, useOutletContext, useParams } from "react-router-dom"; import { Signer, ethers } from "ethers"; import Safe, { EthersAdapter } from "@safe-global/protocol-kit"; import { contractNetworks } from "../chains"; import { Button, View, Text } from "reshaped"; import { Address } from "viem"; +import { NetworkContext, SafeContext, SafeInformationType } from "../components/Contexts"; type SafeData = Awaited>; @@ -35,27 +29,12 @@ async function getSafeSDK(safeAddress: string, signer: Signer) { return { safeSdk, safeSdk2, signer }; } -type SafeInformationType = { - owners: string[]; - threshold: number; - chainId: number; - nonce: number; - address: Address; - safeSdk: Safe; - safeSdk2: Safe; -}; - -export const SafeInformationContext = createContext< - SafeInformationType | undefined ->(undefined); - const useLoadSafeInformation = ({ safeData, }: { safeData: SafeData | undefined; }) => { const [safeInformation, setSafeInformation] = useState(); - useEffect(() => { if (!safeData) return; @@ -79,7 +58,7 @@ const useLoadSafeInformation = ({ }; loadSafeInfo(); - }, [safeData]); + }, [safeData, setSafeInformation]); return safeInformation; }; @@ -87,8 +66,7 @@ const useLoadSafeInformation = ({ export const ViewSafe = () => { const params = useParams(); const [safeData, setSafeData] = useState(); - const providerContext = useContext(WalletProviderContext); - // const currentNetwork = useContext(CurrentNetwork); + const { walletProvider: providerContext } = useOutletContext(); const setupSafe = useCallback(async () => { if (params.safeAddress && providerContext) { @@ -117,17 +95,22 @@ export const ViewSafe = () => { const safeInformation = useLoadSafeInformation({ safeData }); + const safeInformationContext: SafeContext |undefined = useMemo(() => { + if (!safeInformation) return; + return { + safeInformation + }; + }, [safeInformation]); + return ( - - - View Safe - - {safeData ? ( - - ) : ( - - )} - - + + View Safe + + {safeInformationContext ? ( + + ) : ( + + )} + ); }; diff --git a/src/app/Wrapper.tsx b/src/app/Wrapper.tsx index 46c623a..6392fbb 100644 --- a/src/app/Wrapper.tsx +++ b/src/app/Wrapper.tsx @@ -14,12 +14,12 @@ const router = createHashRouter([ Component: Root, children: [ { - path: "/", + path: "/safe/:networkId", index: true, Component: App, }, { - path: "/create", + path: "/safe/:networkId/create", Component: CreateSafe, }, { diff --git a/src/components/AddressView.tsx b/src/components/AddressView.tsx index a250afb..7db2c35 100644 --- a/src/components/AddressView.tsx +++ b/src/components/AddressView.tsx @@ -1,6 +1,6 @@ -import { useContext } from "react"; -import { CurrentNetwork } from "../app/Root"; import { goerli, mainnet, optimism, zora, zoraTestnet } from "viem/chains"; +import { useOutletContext } from "react-router-dom"; +import { NetworkContext } from "./Contexts"; export const networkToExplorer = { [mainnet.id]: "https://etherscan.io/", @@ -17,7 +17,7 @@ export const AddressView = ({ address: `0x${string}`; prettyName?: string; }) => { - const currentNetwork = useContext(CurrentNetwork); + const currentNetwork = useOutletContext().currentNetwork; // const { data: ensName } = useEns({ address, enabled: !prettyName }); const ensName = null; diff --git a/src/components/Contexts.ts b/src/components/Contexts.ts new file mode 100644 index 0000000..5be8b52 --- /dev/null +++ b/src/components/Contexts.ts @@ -0,0 +1,23 @@ +import Safe from "@safe-global/protocol-kit"; +import { BrowserProvider } from "ethers"; +import { Address } from "viem"; + +export type SafeInformationType = { + owners: string[]; + threshold: number; + chainId: number; + nonce: number; + address: Address; + safeSdk: Safe; + safeSdk2: Safe; +}; + +export interface NetworkContext { + walletProvider: BrowserProvider; + currentNetwork: number; + +} + +export interface SafeContext { + safeInformation: SafeInformationType; +} diff --git a/src/components/DataActionPreview.tsx b/src/components/DataActionPreview.tsx index cc5ddc9..d11f6ee 100644 --- a/src/components/DataActionPreview.tsx +++ b/src/components/DataActionPreview.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { Address, Hex } from "viem"; import { base, @@ -9,7 +9,8 @@ import { zora, zoraTestnet, } from "viem/chains"; -import { CurrentNetwork } from "../app/Root"; +import { useOutletContext } from "react-router-dom"; +import { NetworkContext } from "./Contexts"; const networkToEtherActor: any = { [mainnet.id]: "mainnet", @@ -22,7 +23,7 @@ const networkToEtherActor: any = { }; export const DataActionPreview = ({ to, data }: { to: Address; data: Hex }) => { - const currentNetwork = useContext(CurrentNetwork); + const currentNetwork = useOutletContext().currentNetwork; const [responseData, setResponseData] = useState(); const fetchData = useCallback(async () => { diff --git a/src/components/NetworkSwitcher.tsx b/src/components/NetworkSwitcher.tsx index d251ae7..8adf6cc 100644 --- a/src/components/NetworkSwitcher.tsx +++ b/src/components/NetworkSwitcher.tsx @@ -1,52 +1,37 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { useCallback, useEffect } from "react"; +import { useCallback, useState } from "react"; import { FormControl, Select } from "reshaped"; import { allowedNetworks } from "../chains"; -import { BrowserProvider } from "ethers"; +import { useNavigate } from "react-router-dom"; export const NetworkSwitcher = ({ currentNetwork, - setCurrentNetwork, - provider, }: { - currentNetwork: number; - setCurrentNetwork: (chainId: number) => void; - provider: BrowserProvider | undefined; + currentNetwork: string | undefined; }) => { + const [currentNetworkValue, setCurrentNetworkValue] = useState(currentNetwork); + const navigate = useNavigate(); const changeNetwork = useCallback( - ({ value }: { value: string }) => { - provider?.send("wallet_switchEthereumChain", [ - { - chainId: `0x${parseInt(value).toString(16)}`, - }, - ]); + (e: { value: string }) => { + const networkId = e.value; + setCurrentNetworkValue(networkId); + + navigate(`/safe/${networkId}`); }, - [provider], + [navigate] ); - useEffect(() => { - const handleChainChanged = (chainId: unknown) => { - setCurrentNetwork(parseInt(chainId as string)); - }; - // @ts-ignore - window.ethereum?.on("chainChanged", handleChainChanged); - - return () => { - // @ts-ignore - window.ethereum?.removeListener("chainChanged", handleChainChanged); - }; - }); - return ( Network: