From 2ef5d1bf3f4fcd0e1e893c2c933288ad8d00c3a2 Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Wed, 18 Oct 2023 15:28:49 +1100 Subject: [PATCH] feat: ssr --- packages/core/src/createConfig.ts | 26 ++++++++++++++++++--- packages/core/src/exports/index.test.ts | 1 + packages/core/src/exports/index.ts | 5 ++++ packages/core/src/rehydrate.ts | 16 +++++++++++++ packages/react/src/context.ts | 18 +++++++------- packages/react/src/rehydrate.ts | 31 +++++++++++++++++++++++++ playgrounds/vite-ssr-react/lib/wagmi.ts | 1 + 7 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/rehydrate.ts create mode 100644 packages/react/src/rehydrate.ts diff --git a/packages/core/src/createConfig.ts b/packages/core/src/createConfig.ts index 2c993e2cdc..eaf517bb87 100644 --- a/packages/core/src/createConfig.ts +++ b/packages/core/src/createConfig.ts @@ -1,4 +1,8 @@ -import { type EIP6963ProviderDetail, createStore as createMipd } from 'mipd' +import { + type EIP6963ProviderDetail, + type Store as MipdStore, + createStore as createMipd, +} from 'mipd' import { type Address, type Chain, @@ -8,7 +12,7 @@ import { createClient, } from 'viem' import { persist, subscribeWithSelector } from 'zustand/middleware' -import { createStore } from 'zustand/vanilla' +import { type Mutate, type StoreApi, createStore } from 'zustand/vanilla' import { type ConnectorEventMap, @@ -33,6 +37,7 @@ export type CreateConfigParameters< connectors?: CreateConnectorFn[] | undefined multiInjectedProviderDiscovery?: boolean | undefined storage?: Storage | null | undefined + ssr?: boolean | undefined syncConnectedChain?: boolean | undefined } & OneOf< | ({ transports: transports } & { @@ -66,6 +71,7 @@ export function createConfig< : noopStorage, }), syncConnectedChain = true, + ssr, ...rest } = parameters @@ -81,7 +87,9 @@ export function createConfig< const connectors = createStore(() => [ ...(rest.connectors ?? []), - ...(mipd?.getProviders().map(providerDetailToConnector) ?? []), + ...(!ssr + ? mipd?.getProviders().map(providerDetailToConnector) ?? [] + : []), ].map(setup), ) function setup(connectorFn: CreateConnectorFn) { @@ -174,10 +182,12 @@ export function createConfig< name: 'store', partialize(state) { return { + connections: state.connections, chainId: state.chainId, current: state.current, } satisfies PartializedState }, + skipHydration: ssr, storage: storage as Storage>, version: 1, }) @@ -318,9 +328,13 @@ export function createConfig< }, _internal: { + mipd, + store, + ssr: Boolean(ssr), syncConnectedChain, transports: rest.transports as transports, connectors: { + providerDetailToConnector, setup, setState: (value) => connectors.setState( @@ -369,10 +383,16 @@ export type Config< }): Client> _internal: { + readonly mipd: MipdStore | undefined + readonly store: Mutate, [['zustand/persist', any]]> + readonly ssr: boolean readonly syncConnectedChain: boolean readonly transports: transports connectors: { + providerDetailToConnector( + providerDetail: EIP6963ProviderDetail, + ): CreateConnectorFn setup(connectorFn: CreateConnectorFn): Connector setState(value: Connector[] | ((state: Connector[]) => Connector[])): void subscribe( diff --git a/packages/core/src/exports/index.test.ts b/packages/core/src/exports/index.test.ts index 87fae37671..161194da6b 100644 --- a/packages/core/src/exports/index.test.ts +++ b/packages/core/src/exports/index.test.ts @@ -66,6 +66,7 @@ test('exports', () => { "createConfig", "createStorage", "noopStorage", + "rehydrate", "BaseError", "ChainMismatchError", "ChainNotConfiguredError", diff --git a/packages/core/src/exports/index.ts b/packages/core/src/exports/index.ts index 038b3f9771..37d34692b2 100644 --- a/packages/core/src/exports/index.ts +++ b/packages/core/src/exports/index.ts @@ -344,6 +344,11 @@ export { noopStorage, } from '../createStorage.js' +//////////////////////////////////////////////////////////////////////////////// +// rehydrate + +export { rehydrate } from '../rehydrate.js' + //////////////////////////////////////////////////////////////////////////////// // Errors diff --git a/packages/core/src/rehydrate.ts b/packages/core/src/rehydrate.ts new file mode 100644 index 0000000000..20983cc19c --- /dev/null +++ b/packages/core/src/rehydrate.ts @@ -0,0 +1,16 @@ +import type { Config } from './createConfig.js' + +export async function rehydrate(config: Config) { + if (!config._internal.ssr) return + + await config._internal.store.persist.rehydrate() + + const mipdConnectors = config._internal.mipd + ?.getProviders() + .map(config._internal.connectors.providerDetailToConnector) + .map(config._internal.connectors.setup) + config._internal.connectors.setState((connectors) => [ + ...connectors, + ...(mipdConnectors ?? []), + ]) +} diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts index ec3dc055d2..8b60ac4663 100644 --- a/packages/react/src/context.ts +++ b/packages/react/src/context.ts @@ -1,7 +1,8 @@ 'use client' -import { type ResolvedRegister, reconnect } from '@wagmi/core' -import { createContext, createElement, useEffect } from 'react' +import { type ResolvedRegister } from '@wagmi/core' +import { createContext, createElement } from 'react' +import { Rehydrate } from './rehydrate.js' export const WagmiContext = createContext< ResolvedRegister['config'] | undefined @@ -15,13 +16,12 @@ export type WagmiProviderProps = { export function WagmiProvider( parameters: React.PropsWithChildren, ) { - const { children, config, reconnectOnMount = true } = parameters - - // biome-ignore lint/nursery/useExhaustiveDependencies: only run on mount - useEffect(() => { - if (reconnectOnMount) reconnect(config) - }, []) + const { children, config } = parameters const props = { value: config } - return createElement(WagmiContext.Provider, props, children) + return createElement( + Rehydrate, + parameters, + createElement(WagmiContext.Provider, props, children), + ) } diff --git a/packages/react/src/rehydrate.ts b/packages/react/src/rehydrate.ts new file mode 100644 index 0000000000..e64ebfa4bb --- /dev/null +++ b/packages/react/src/rehydrate.ts @@ -0,0 +1,31 @@ +'use client' + +import { type ResolvedRegister, reconnect, rehydrate } from '@wagmi/core' +import { useEffect, useRef } from 'react' + +export type RehydrateProps = { + config: ResolvedRegister['config'] + reconnectOnMount?: boolean | undefined +} + +export function Rehydrate(parameters: React.PropsWithChildren) { + const { children, config, reconnectOnMount = true } = parameters + + if (!config._internal.ssr && reconnectOnMount) reconnect(config) + + const active = useRef(true) + // biome-ignore lint/nursery/useExhaustiveDependencies: + useEffect(() => { + if (!active.current) return + if (!config._internal.ssr) return + ;(async () => { + await rehydrate(config) + reconnect(config) + })() + return () => { + active.current = false + } + }, []) + + return children +} diff --git a/playgrounds/vite-ssr-react/lib/wagmi.ts b/playgrounds/vite-ssr-react/lib/wagmi.ts index cfcd713ad8..e3b7c08772 100644 --- a/playgrounds/vite-ssr-react/lib/wagmi.ts +++ b/playgrounds/vite-ssr-react/lib/wagmi.ts @@ -17,6 +17,7 @@ export const config = createConfig({ ledger({ projectId: import.meta.env.VITE_WC_PROJECT_ID }), safe({ debug: true, shimDisconnect: true }), ], + ssr: true, storage: createStorage({ key: 'vite-ssr-react', storage: typeof localStorage !== 'undefined' ? localStorage : undefined,