Icons made by Pixel perfect from www.flaticon.com
Tiny library to use feature flags in React. Get features by its slug identifier or get a binary output using flag queries.
NPM:
npm i toggled
Yarn:
yarn add toggled
Internal public interface used by default to type the <FeatureProvider />
and useFeature
.
export interface DefaultFeature {
slug: string
}
// src/types/toggled.d.ts
import 'toggled'
// extend the interface if needed
declare module 'toggled' {
export interface DefaultFeature {
slug: string
settings?: any
}
}
It could be the feature slug or an flag queries array or more powerful, an object query.
type FlagQuery =
| string
| FlagQuery[]
| {
$or: FlagQuery[]
}
| {
$and: FlagQuery[]
}
| {
[slug: string]: boolean
}
// src/constants/domain.ts
import { Op } from 'toggled'
// Note that each entry is a `FlagQuery`
export const flagQueries: Record<string, FlagQuery> = {
// True if the slug is in the context
FF_1: 'ff-1',
// True if all the slugs are in the context
FF_2_FULL: ['ff-2.1', 'ff-2.2'],
// True if `'ff-2.1'` is in the context and `'ff-2.2'` is not
FF_2_1_ONLY: {
'ff-2.1': true,
'ff-2.2': false,
},
// True if `'ff-3.1'` **or** `'ff-3.2'` is in the context
FF_3_X: {
[Op.OR]: ['ff-3.1', 'ff-3.2'],
},
// True if `'ff-4.1'` **and** `'ff-4.2'` are in the context
FF_4_FULL: {
[Op.AND]: ['ff-4.1', 'ff-4.2'],
},
// True if all the previous queries are true
COMPLEX: {
FF_1: 'ff-1',
FF_2_FULL: ['ff-2.1', 'ff-2.2'],
FF_2_1_ONLY: {
'ff-2.1': true,
'ff-2.2': false,
},
FF_3_X: {
[Op.OR]: ['ff-3.1', 'ff-3.2'],
},
FF_4_FULL: {
[Op.AND]: ['ff-4.1', 'ff-4.2'],
},
},
}
Library context, exported for no specific reason, avoid using it and prefer the custom hooks, or open a PR to add a new one that obligates you to use the FeatureContext
.
interface FeatureContextValue<F extends DefaultFeature = DefaultFeature> {
cache: Map<string, F>
}
Provider component that exposes the features in a more convenient way to get them by its own slugs.
interface FeatureProviderProps<F extends DefaultFeature = DefaultFeature> {
features: F[]
children: React.ReactNode
}
import { FeatureProvider } from 'toggled'
import apiClient from './api-client'
import App from './app'
apiClient.getAllFeatures().then(features => {
ReactDOM.render(
<FeatureProvider features={features}>
<App />
</FeatureProvider>,
document.getElementById('root'),
)
})
Hook that is used to get a feature object from the context by its slug. Notice that it could be undefined
because the context only should contain the features that are enabled.
interface UseFeature<F extends DefaultFeature = DefaultFeature> {
(slug: string): F | undefined
}
// src/app.tsx
import { useFeature } from 'toggled'
function App() {
const themeFF = useFeature('theme')
return (
<ThemeProvider theme={themeFF ? themeFF.settings.name : 'default'}>
<Component />
</ThemeProvider>
)
}
Hook that is used to get the magic function that can process a flag query.
interface UseFlagQueryFn {
(): (query: FlagQuery) => boolean
}
import { useFlagQueryFn } from 'toggled'
export default function App() {
const flagQueryFn = useFlagQueryFn()
return (
<Layout designV2={flagQueryFn({ 'design-v2': true, 'design-v1': false })}>
{flagQueryFn('chat') && <ChatWidget>}
</Layout>
)
}
For more use cases, please go to the tests.
Hook that is used to get a binary output based on the existence of a feature in the context. So, if the feature is in the context then the flag will be true
, otherwise false
.
The
useFlagQueryFn
hook is used internally.
interface UseFlag {
(query: FlagQuery): boolean
}
import { useFlag } from 'toggled'
export default function App() {
const hasChat = useFlag('chat')
const hasDesignV2Only = useFlag({ 'design-v2': true, 'design-v1': false })
return (
<Layout designV2={hasDesignV2Only}>
{hasChat && <ChatWidget>}
</Layout>
)
}
Component to apply conditional rendering using a flagQuery
interface FlagProps {
flagQuery: FlagQuery
children: React.ReactNode
}
import { Flag } from 'toggled'
export default function App() {
return (
<Flag flagQuery={{ 'design-v2': true, 'design-v1': false }}>
<Layout designV2={hasDesignV2Only}>
<Flag flagQuery="chat">
<ChatWidget>
</Flag>
</Layout>
</Flag>
)
}
Component to consume a feature object declaratively instead of useFeature
export interface FeatureProps {
slug: string
children(feature: DefaultFeature): React.ReactElement
}
import { Feature } from 'toggled'
export default function App() {
return (
<Feature slug="chat">
{feature => {
return <ChatWidget settings={feature.settings}>
}}
</Feature>
)
}
MIT © Ricardo Q. Bazan