Skip to content

Commit

Permalink
add flags context
Browse files Browse the repository at this point in the history
  • Loading branch information
dferber90 committed Feb 14, 2025
1 parent a96464e commit 8b7deca
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 0 deletions.
26 changes: 26 additions & 0 deletions examples/snippets/app/examples/flag-context/flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ReadonlyRequestCookies } from '@vercel/flags';
import { flag, dedupe } from '@vercel/flags/next';

interface Entities {
user?: { id: string };
}

const identify = dedupe(
({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
const userId = cookies.get('dashboard-user-id')?.value;
return { user: userId ? { id: userId } : undefined };
},
);

export const dashboardFlag = flag<boolean, Entities>({
key: 'dashboard-flag',
identify,
description: 'Flag used on the Dashboard Pages example',
decide({ entities }) {
if (!entities?.user) return false;
// Allowed users could be loaded from Edge Config or elsewhere
const allowedUsers = ['user1'];

return allowedUsers.includes(entities.user.id);
},
});
16 changes: 16 additions & 0 deletions examples/snippets/app/examples/flag-context/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FlagContextProvider } from '@vercel/flags/react';
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>
);
}
47 changes: 47 additions & 0 deletions examples/snippets/app/examples/flag-context/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';

import { Button } from '@/components/ui/button';
import { useFlagContext } from '@vercel/flags/react';
import { DemoFlag } from '@/components/demo-flag';
import { useRouter } from 'next/navigation';
import type { FlagValuesType } from '@vercel/flags';

export default function Page() {
const dashboard = useFlagContext<boolean>('dashboard-flag');
const router = useRouter();
return (
<>
<DemoFlag name="dashboard-flag" value={dashboard} />
<div className="flex gap-2">
<Button
onClick={() => {
document.cookie = 'dashboard-user-id=user1; path=/';
router.refresh();
}}
variant="outline"
>
Act as a flagged in user
</Button>
<Button
onClick={() => {
document.cookie = 'dashboard-user-id=user2; path=/';
router.refresh();
}}
variant="outline"
>
Act as a regular user
</Button>
<Button
onClick={() => {
document.cookie =
'dashboard-user-id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
router.refresh();
}}
variant="outline"
>
Clear cookie
</Button>
</div>
</>
);
}
27 changes: 27 additions & 0 deletions packages/flags/src/react/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import React from 'react';
import type { FlagDefinitionsType, FlagValuesType } from '../types';
import { safeJsonStringify } from '../lib/safe-json-stringify';
Expand Down Expand Up @@ -42,3 +44,28 @@ export function FlagValues({
/>
);
}

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);
}

0 comments on commit 8b7deca

Please sign in to comment.