Skip to content

Add flags context #64

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

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

@mrmckeb
Copy link

mrmckeb commented Apr 2, 2025

We hit a case where we need this today and this solution looks great, thanks! Is this something you still plan to ship?

For now we'll implement our own simple version of it, scoped specifically to our use-case.

@dferber90
Copy link
Collaborator Author

@mrmckeb long time! Great to see you're using the Flags SDK!

I'm a bit hesitant of adding this solution as I think there is ultimately a better way of dealing with this. Not sure what it would look like exactly yet though. I'm unhappy the current solution requires duplicating the names of flags, and having a dynamic layout.

One thing you could do on top of the recommendation above is to add a Suspense boundary and to pass a promise containing the flag state instead of the fully resolved flags to the client. This would speed things up slightly and integrate better with PPR.

@mrmckeb
Copy link

mrmckeb commented Apr 6, 2025

Great, thanks for the additional info - and nice to hear from you ;)

We really enjoy working with the SDK, the team love the toolbar integration too.

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