Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flags context #64

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Add flags context #64

wants to merge 1 commit into from

Conversation

dferber90
Copy link
Collaborator

Adds a flags context for use in dashboard applications where lots of client components need access to the flags state.

The DX of traditional feature flag SDKs which bootstrap on the client is great, as people can simply use hooks to load their flags. But their huge downside is that this leads to poor UX. Loading your flags client-side comes with extra latency, and will lead to layout shift if any flag ever influences the actual rendering. I wrote about their tradeoffs here https://flags-sdk.dev/knowledge-base/server-side-vs-client-side

The following approach should provide the same benefits, without any of the downsides

  • avoids bootstrapping from client / waterfall request
  • avoids layout shift
  • allows overriding flags with Flags Explorer

There are downsides to this approach though

  • feature flags are no longer called as functions but rather need to be referred to by their keys
  • any server components downstream of the layout won't have access to the context, and need to call the flag on their own

1. Use a layout to determine your feature flags, and provide them as context

// layout.tsx
import { FlagContextProvider } from './flag-context'; // shown below
import { dashboardFlag } from './flags';

export default async function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const dashboard = await dashboardFlag();

  return (
    <FlagContextProvider values={{ [dashboardFlag.key]: dashboard }}>
      {children}
    </FlagContextProvider>
  );
}

2. Consume your flags in any client component

// page.tsx
'use client';

import { useFlagContext } from './flag-context';

export default function Page() {
  // provide a key to read a single flag
  const dashboard = useFlagContext<boolean>('dashboard-flag');

  // provide no key to read all flags
  const allFlags = useFlagContext<{ "dashboard-flag": boolean }>();

  const router = useRouter();
  return <>{dashboard ? "dashboard is on" : "dashboard is off"}</>;
}

3. Create a flag context

// flag-context.ts
'use client';

import React from 'react';
import type { FlagValuesType } from '@vercel/flags';

const emptyValues: FlagValuesType = {};
const FlagContext = React.createContext<FlagValuesType>(emptyValues);

export function FlagContextProvider<T>({
  children,
  values,
}: {
  children: React.ReactNode;
  values: FlagValuesType;
}) {
  return <FlagContext.Provider value={values}>{children}</FlagContext.Provider>;
}

export function useFlagContext<T>(): T;
export function useFlagContext<T>(key: string): T;
export function useFlagContext<T>(key?: string): T {
  const values = React.useContext(FlagContext);

  if (values === emptyValues) {
    throw new Error('FlagContextProvider not found');
  }

  return typeof key === 'string' ? (values[key] as T) : (values as T);
}

Copy link

vercel bot commented Feb 14, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
summer-sale ❌ Failed (Inspect) Feb 14, 2025 5:24am

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.

1 participant