npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@equinor/fusion-framework-react-app

v14.1.2

Published

Readme

@equinor/fusion-framework-react-app

React bindings for building modular Fusion Framework applications. Provides rendering helpers, configuration callbacks, and React hooks for accessing framework modules (HTTP, auth, context, navigation, bookmarks, settings, and more).

Features

  • One-line app bootstrap via renderApp — creates a render function that mounts your component with React 18's createRoot
  • Module hooksuseAppModule / useAppModules for type-safe access to any registered module
  • Sub-path entry-points for optional capabilities: MSAL auth, HTTP, context, navigation, bookmarks, feature flags, settings, analytics, AG Grid theming, help center, and apploader
  • Environment variables hook — useAppEnvironmentVariables for accessing app config at runtime
  • Lazy loading — components are wrapped in React.lazy + Suspense automatically

Installation

pnpm add @equinor/fusion-framework-react-app

Usage

Bootstrap an application

// config.ts
import type { AppModuleInitiator } from '@equinor/fusion-framework-react-app';

export const configure: AppModuleInitiator = (configurator) => {
  configurator.configureHttpClient('my-api', {
    baseUri: 'https://api.example.com',
    defaultScopes: ['api://my-api/.default'],
  });
};

// App.tsx
export const App = () => <h1>Hello Fusion</h1>;

// index.ts
import { renderApp } from '@equinor/fusion-framework-react-app';
import { App } from './App';
import { configure } from './config';

export const render = renderApp(App, configure);
export default render;

Access a module

import { useAppModule } from '@equinor/fusion-framework-react-app';

const MyComponent = () => {
  const auth = useAppModule('auth');
  // ...
};

HTTP requests

import { useHttpClient } from '@equinor/fusion-framework-react-app/http';

const MyComponent = () => {
  const client = useHttpClient('my-api');
  // client.fetch$('endpoint').subscribe(...)
  // await client.fetchAsync('endpoint')
};

MSAL authentication

Note: Requires @equinor/fusion-framework-module-msal. The MSAL module must be configured by the host/portal — apps should only consume the hooks.

import { useCurrentAccount, useAccessToken } from '@equinor/fusion-framework-react-app/msal';

const UserInfo = () => {
  const user = useCurrentAccount();
  const { token } = useAccessToken({ scopes: ['User.Read'] });
  if (!user) return <p>Not signed in</p>;
  return <p>Welcome, {user.name}</p>;
};

Context

import { useCurrentContext } from '@equinor/fusion-framework-react-app/context';

const ContextInfo = () => {
  const context = useCurrentContext();
  return <p>Selected: {context?.title ?? 'none'}</p>;
};

Navigation / routing

import { useRouter } from '@equinor/fusion-framework-react-app/navigation';
import { RouterProvider } from '@equinor/fusion-framework-react-router';

const routes = [{ path: '/', element: <Home /> }];

const App = () => {
  const router = useRouter(routes);
  return <RouterProvider router={router} />;
};

State Management

The Fusion Framework provides a powerful state management solution that enables persistent, cross-component state sharing with automatic synchronization. Unlike traditional React state that's lost on page refresh, this state persists across app sessions and stays synchronized between different components in real-time.

Key Benefits:

  • 🔄 Persistent State: Survives page refreshes and app restarts
  • 🔗 Cross-Component Sync: Share state between any components instantly
  • Optimistic Updates: Responsive UI with automatic rollback on errors
  • 🛡️ Type Safe: Full TypeScript support with type inference
  • 🎯 Simple API: Works like useState but with persistence

Use Cases:

  • User preferences and settings
  • Form data that should persist
  • UI state like filters, sorting, or view modes
  • Data that needs to be shared across multiple components
  • Cache management for expensive operations

Installation

First, install the state module package:

pnpm install @equinor/fusion-framework-module-state

Setup

Enable the state module in your app configuration. This initializes the persistent storage and makes useAppState available throughout your application:

import { enableAppState } from '@equinor/fusion-framework-react-app/state';
export const configure: ModuleInitiator = (appConfigurator) => {
  enableAppState(appConfigurator);
};

[!CAUTION] The state management module is a powerful tool, but it's important to know the potential pitfalls and limitations when using it in your application. The state management is global and can lead to unexpected behavior if not used carefully.

example 1: If you have multiple components that rely on the same state, updating the state in one component can cause re-renders in all components that use that state, potentially leading to performance issues.

example 2: The user has open multiple tabs of the application, and each tab is modifying the same state. This can lead to unexpected behavior, as changes made in one tab will be reflected to all tabs. (like storing user preferences for selected columns)

Basic Usage

Use useAppState just like React's useState, but with automatic persistence. The first parameter is a unique key, and the second is an options object with the default value:

import { useAppState } from '@equinor/fusion-framework-react-app/state';

const Counter = () => {
  const [count, setCount] = useAppState('counter', { defaultValue: 0 });
  return (
    <div>
      <span>Count: {count}</span>
      <button onClick={() => setCount((prev) => (prev ?? 0) + 1)}>Increment</button>
    </div>
  );
};

Cross-Component Synchronization

Multiple components can share the same state by using the same key. Changes in one component automatically update all others:

import { useAppState } from '@equinor/fusion-framework-react-app/state';

const Incrementer = () => {
  const [count, setCount] = useAppState('counter', { defaultValue: 0 });
  return (
    <button onClick={() => setCount((prev) => (prev || 0) + 1)}>
      Increment
    </button>
  );
};

const Display = () => {
  const [count] = useAppState('counter', { defaultValue: 0 });
  return <span>Current count: {count}</span>;
};

// Usage in your app
const App = () => (
  <div>
    <Incrementer />
    <Display />
  </div>
);

Advanced Usage

Complex Objects with TypeScript:

interface UserPreferences {
  theme: 'light' | 'dark';
  language: string;
  notifications: boolean;
}

const SettingsPanel = () => {
  const [settings, setSettings] = useAppState<UserPreferences>('user-settings', {
    defaultValue: { theme: 'light', language: 'en', notifications: true }
  });

  const toggleTheme = () => {
    setSettings(prev => ({
      ...prev!,
      theme: prev!.theme === 'light' ? 'dark' : 'light'
    }));
  };

  return <button onClick={toggleTheme}>Theme: {settings?.theme}</button>;
};

Clearing State:

// Remove from storage completely
const clearSettings = () => setSettings(undefined);

Best Practices

Avoid Stale Closures

[!WARNING] When updating state based on the current value, always use the updater function to prevent stale closure issues in concurrent updates.

const [count, setCount] = useAppState('counter', { defaultValue: 0 });

// ❌ Bad: Can use stale value in rapid updates
const increment = () => setCount(count + 1);

// ✅ Good: Always gets the latest value
const increment = () => setCount(prev => (prev || 0) + 1);

State Key Organization

Use hierarchical naming for better organization:

// ✅ Good - hierarchical, descriptive
'user.profile.personal'
'user.preferences.theme'
'app.settings.notifications'
'feature.dashboard.filters'

// ❌ Avoid - flat, unclear
'userdata'
'settings'
'stuff'

Use strong typing

// ✅ Good - strong typing
interface UserProfile {
  id: string;
  name: string;
  email: string;
}

const [user, setUser] = useAppState<UserProfile>('user.profile');

// ❌ Avoid - weak typing
const [user, setUser] = useAppState('user.profile');

[!TIP] Validate Complex Schemas Use a library like zod or yup to validate complex state schemas before using them.

const userSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(100),
  email: z.string().email(),
});

type UserProfile = z.infer<typeof userSchema>;

// ✅ Good - strong typing with validation
const useMyUser = () => {
  const [value, setValue] = useAppState<UserProfile>('user.profile');
  const setUser = useCallback((user: UserProfile) => {
    if (userSchema.safeParse(user).success) {
      setValue(user);
      return true;
    } else {
      console.warn('Provided user is invalid');
      return false;
    }
  }, [setValue]);
  if(!userSchema.safeParse(value).success) {
    console.warn('Current user state is invalid');
    return null;
  }
  return value;
};

Feature Flag

Feature flags

Note: Requires @equinor/fusion-framework-module-feature-flag.

import { enableFeatureFlag } from '@equinor/fusion-framework-react-app/feature-flag';

export const configure: AppModuleInitiator = (configurator) => {
  enableFeatureFlag(configurator, [
    { key: 'dark-mode', title: 'Dark mode' },
    { key: 'beta-ui', title: 'Beta UI', allowUrl: true },
  ]);
};
import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag';

const Toggle = () => {
  const { feature, toggleFeature } = useFeature('dark-mode');
  return <Switch checked={feature?.enabled} onChange={() => toggleFeature()} />;
};

Bookmarks

import { useCurrentBookmark } from '@equinor/fusion-framework-react-app/bookmark';

const BookmarkView = () => {
  const { currentBookmark } = useCurrentBookmark();
  return <pre>{JSON.stringify(currentBookmark, null, 2)}</pre>;
};

Settings

import { useAppSetting } from '@equinor/fusion-framework-react-app/settings';

const ThemePicker = () => {
  const [theme, setTheme] = useAppSetting('theme', 'light');
  return <button onClick={() => setTheme('dark')}>Go dark</button>;
};

Environment variables

import { useAppEnvironmentVariables } from '@equinor/fusion-framework-react-app';

const EnvInfo = () => {
  const env = useAppEnvironmentVariables();
  if (!env.complete) return <p>Loading…</p>;
  return <pre>{JSON.stringify(env.value, null, 2)}</pre>;
};

Testing

Testing utilities (renderAppHook, renderAppComponent, testApp, and a Vitest plugin resolving an app's own manifest/config as virtual modules) have moved to their own package, @equinor/fusion-framework-vitest-plugin-react-app — see that package's README for usage.

API Reference

Core exports (main entry-point)

| Export | Description | | --- | --- | | renderApp | Creates a mount function from a component and optional config callback | | makeComponent | Lazily initialises modules and wraps a component in framework providers | | createComponent | (deprecated) Factory returning a ComponentRenderer | | createLegacyApp | (deprecated) Wrapper for legacy Fusion CLI apps | | renderComponent | Lower-level helper: mounts a ComponentRenderer via createRoot | | useAppModule(key) | Returns a single module instance by key | | useAppModules() | Returns all initialised application modules | | useAppEnvironmentVariables() | Observable state of the app's environment config |

Sub-path entry-points

| Path | Key exports | | --- | --- | | /msal | useCurrentAccount, useAccessToken, useToken | | /http | useHttpClient (re-export) | | /http/selectors | Response selector utilities | | /context | useCurrentContext, useContextProvider, useFrameworkCurrentContext | | /navigation | useRouter, useNavigationModule | | /feature-flag | enableFeatureFlag, useFeature | | /bookmark | enableBookmark, useCurrentBookmark, useBookmark | | /settings | useAppSetting, useAppSettings | | /analytics | useTrackFeature | | /help-center | useHelpCenter | | /apploader | Apploader, useApploader | | /framework | useFramework, useCurrentUser, useFrameworkHttpClient | | /widget | Widget entry-point |

Configuration

Application configuration is done via a callback passed to renderApp (or makeComponent). The callback receives an IAppConfigurator with builders for each module:

const configure: AppModuleInitiator = (configurator) => {
  // HTTP clients
  configurator.configureHttpClient('my-api', { baseUri: '...' });

  // Feature flags
  enableFeatureFlag(configurator, [{ key: 'beta', title: 'Beta' }]);

  // Navigation, context, etc. — see module docs
};

The MSAL / auth module is configured by the host portal and hoisted to apps automatically. Do not configure it in application code.