Docs
useEnvironment

useEnvironment / EnvironmentProvider

Relay is opt-in. @apollion-dsi/relay is the recommended path only if your app uses Relay. Apollion for SSR — or any non-Relay consumer — never needs this package: @apollion-dsi/core carries no Relay dependency and no core component requires a Relay environment. Themes and rendering work with zero Relay. Everything below applies once you have chosen Relay.

The single Relay provider for an app. EnvironmentProvider publishes the Relay Environment to the whole React tree and backs both hook systems at once:

  • the DS useEnvironment() hook — for imperative access to the Environment (fetchQuery, requestSubscription, the Promise-based commitMutation);
  • every react-relay store hook — useLazyLoadQuery, usePreloadedQuery, useFragment, useSubscription.

Internally it mounts react-relay's RelayEnvironmentProvider over the same Environment, so consumers never import or mount RelayEnvironmentProvider themselves — that is package plumbing the DS owns. This is why the Relay integration lives in the design system: EnvironmentProvider is the single seam where DS themes and Relay meet. Pair it with CreateRelayEnvironment: the Environment the factory exposes goes into one EnvironmentProvider at the app root, and everything below reads it — imperatively or through Relay hooks.

The provider also accepts a MockEnvironment from relay-test-utils, so the backend swaps out in tests with no extra wrappers — the same single mount drives useEnvironment() and the store hooks under the mock.

When to use

✅ Use when…🚫 Avoid when…
  • At the app root, as the one provider for all Relay data — both the imperative Environment and the react-relay store/subscription hooks resolve under it.
  • In tests, to inject a MockEnvironment the same way the real one is injected — both hook systems included.
  • Do not also mount react-relay's RelayEnvironmentProvider yourself — EnvironmentProvider already does. (Mounting it again over the same Environment is harmless/idempotent, but it is redundant.)
  • Below the root: mount it once. Nested EnvironmentProviders just shadow the environment for their subtree.

Example — one provider, both hook systems

import { CreateRelayEnvironment, EnvironmentProvider, useEnvironment } from '@apollion-dsi/relay';
import { graphql, useLazyLoadQuery } from 'react-relay';
 
const { Environment } = new CreateRelayEnvironment({ url: '...' });
 
function App() {
  return (
    <EnvironmentProvider environment={Environment}>
      <Profile />
      <SaveButton />
    </EnvironmentProvider>
  );
}
 
// react-relay store hook — resolves under EnvironmentProvider, no
// RelayEnvironmentProvider needed.
function Profile() {
  const data = useLazyLoadQuery(graphql`
    query ReadmeProfileQuery {
      viewer { username }
    }
  `, {});
  return <span>{data.viewer?.username}</span>;
}
 
// DS imperative hook — reads the same Environment.
function SaveButton() {
  const { environment } = useEnvironment();
  return <button onClick={() => commitMutation(environment, { ... })}>Save</button>;
}

Backward compatibility

Consumers that still wrap their tree in react-relay's RelayEnvironmentProvider over the same Environment keep working: mounting it twice is idempotent (the nearest provider wins and it is the same instance). Migration is therefore a deletion — drop your own RelayEnvironmentProvider and keep only EnvironmentProvider.

Granular imports

import { useEnvironment, EnvironmentProvider } from '@apollion-dsi/relay/useEnvironment';
// or via the root barrel
import { useEnvironment, EnvironmentProvider } from '@apollion-dsi/relay';

See also