Skip to content

feat: add ConfigContext with Zustand store for instance-scoped configuration#3973

Open
DBosley wants to merge 2 commits into
developmentfrom
config-context-infrastructure
Open

feat: add ConfigContext with Zustand store for instance-scoped configuration#3973
DBosley wants to merge 2 commits into
developmentfrom
config-context-infrastructure

Conversation

@DBosley

@DBosley DBosley commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

Summary

  • Introduces React Context pattern (ConfigContext, useConfig() hook) for config access
  • Adds Zustand store for selector-based subscriptions (useConfigSelector()) to enable granular re-renders
  • Enables multiple WormholeConnect instances on the same page with isolated state
  • Backwards compatible: singleton remains functional via legacy bridge during incremental migration

Changes

File Change
src/contexts/ConfigContext.tsx ConfigProvider with Zustand integration, useConfig/useSetConfig hooks
src/store/configStore.ts Zustand store with useConfigSelector/useConfigValue hooks
src/config/index.ts Legacy bridge (updateLegacyStore) for setConfig() compatibility
src/WormholeConnect.tsx Wraps children with ConfigProvider
src/AppRouter.tsx Example migration to useConfig()
package.json Add zustand as dev + peer dependency

Test plan

  • Build passes: npm run build:lib
  • All 425 tests pass: npm run test:unit
  • New tests verify Zustand store functionality (18 tests)
  • New tests verify ConfigContext integration (17 tests)
  • Pre-commit hooks pass

Key Features

Selector-based subscriptions

// Only re-renders when network changes
const network = useConfigSelector((c) => c.network);

// Only re-renders when chains change  
const chains = useConfigSelector((c) => c.chainsArr);

Multi-instance support

Each <WormholeConnect> widget gets its own isolated Zustand store.

Legacy compatibility

setConfig() calls sync to the active Zustand store via updateLegacyStore().

Migration path

This PR establishes the foundation. Subsequent PRs will:

  1. Migrate React components to useConfigSelector() for optimized re-renders
  2. Add config parameter to utility functions
  3. Remove singleton entirely

@netlify

netlify Bot commented Dec 4, 2025

Copy link
Copy Markdown

Deploy Preview for wormhole-connect-mainnet ready!

Name Link
🔨 Latest commit 7876801
🔍 Latest deploy log https://app.netlify.com/projects/wormhole-connect-mainnet/deploys/6937428a88fe2f0008b40f96
😎 Deploy Preview https://deploy-preview-3973--wormhole-connect-mainnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Dec 4, 2025

Copy link
Copy Markdown

Deploy Preview for wormhole-connect ready!

Name Link
🔨 Latest commit 7876801
🔍 Latest deploy log https://app.netlify.com/projects/wormhole-connect/deploys/6937428a3e859d0009b95a80
😎 Deploy Preview https://deploy-preview-3973--wormhole-connect.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@DBosley
DBosley force-pushed the config-context-infrastructure branch from 24af433 to f6cbde0 Compare December 4, 2025 00:17
@DBosley
DBosley force-pushed the config-context-infrastructure branch from f6cbde0 to 652291d Compare December 4, 2025 00:20
@DBosley
DBosley force-pushed the config-context-infrastructure branch from 652291d to e071690 Compare December 4, 2025 00:22
@DBosley
DBosley force-pushed the config-context-infrastructure branch from e071690 to d2c1f94 Compare December 4, 2025 00:22
@DBosley
DBosley force-pushed the config-context-infrastructure branch from d2c1f94 to 160afc1 Compare December 4, 2025 17:04
@DBosley DBosley changed the title feat: add ConfigContext infrastructure for instance-scoped configuration feat: add ConfigContext with Zustand store for instance-scoped configuration Dec 8, 2025
@DBosley
DBosley force-pushed the config-context-infrastructure branch from f74902e to 539d6c1 Compare December 8, 2025 20:36
Introduces React Context pattern for config access, enabling future support
for multiple WormholeConnect instances on the same page.

Changes:
- Add ConfigContext with ConfigProvider and useConfig() hook
- Wrap WormholeConnect with ConfigProvider
- Update AppRouter to use useConfig() (example migration)
- Add TestConfigContext pattern in testHelpers for test isolation
- Mark singleton export with @deprecated JSDoc
- Fix TypeScript dev server errors (@types/color, env.d.ts)

The singleton remains functional for backwards compatibility.
Migration can proceed incrementally file-by-file.
Adds Zustand-based config store to enable granular re-renders when
config values change. Components can now use useConfigSelector() to
subscribe to specific config properties instead of re-rendering on
any config change.

- Add zustand as dev and peer dependency
- Create configStore.ts with useConfigSelector/useConfigValue hooks
- Integrate Zustand store into ConfigProvider
- Add legacy bridge (updateLegacyStore) for setConfig() compatibility
- Support multiple WormholeConnect instances with isolated state
- Add comprehensive tests for store and context integration
Comment thread src/store/configStore.ts
* Context for providing the store instance to the component tree.
* Components use this to access the correct store for their WormholeConnect instance.
*/
export const ConfigStoreContext = React.createContext<ConfigStore | null>(null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed anymore?

Comment thread src/store/configStore.ts
* Hook to access the config store from Context.
* @throws Error if used outside ConfigProvider
*/
function useConfigStore(): ConfigStore {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed either

Comment thread src/store/configStore.ts
* }));
* ```
*/
export function useConfigSelector<T>(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just be useConfig() inside useConfig.ts.
Consumers will just do const config = useConfig();

Comment thread src/store/configStore.ts
* Hook to get the store's setState function for updating config.
* Used internally by ConfigProvider.
*/
export function useConfigStoreApi(): ConfigStore {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what is this doing

Comment thread src/store/configStore.ts
initialConfig: InternalConfig<Network>,
): ConfigStore {
return createStore<ConfigState>()(() => ({
config: initialConfig,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should break this down to different configs, so that consumers can react to specific changes on sub configs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should also have setters for the config, so that the place where we build the config calls this.

Comment thread src/store/configStore.ts
* Prefer useConfigSelector() for better performance - this hook will
* re-render on ANY config change.
*/
export function useConfigValue(): InternalConfig<Network> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate, but instead might make sense to have useUIConfig() in useUIConfig.ts, useTokensConfig() in useTokensConfig.ts etc, each returns that slice of the state.

Comment thread src/store/configStore.ts
}
}

/**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both the register and unregister can likely move inside the main store's methods? When you call the main store's setXXX method, it should set itself and additionally set legacy stuff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants