2.1.0 • Published 2 years ago

toggled v2.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Tiny library to use feature flags in React. Get features by its slug identifier or get a binary output using flag queries.

Installation

NPM:

npm i toggled

Yarn:

yarn add toggled

API

DefaultFeature Interface

Internal public interface used by default to type the <FeatureProvider /> and useFeature.

Specification

export interface DefaultFeature {
  slug: string
}

Example

// src/types/toggled.d.ts
import 'toggled'

// extend the interface if needed
declare module 'toggled' {
  export interface DefaultFeature {
    slug: string
    settings?: any
  }
}

FlagQuery Type

It could be the feature slug or an flag queries array or more powerful, an object query.

Specification

type FlagQuery =
  | string
  | FlagQuery[]
  | {
      $or: FlagQuery[]
    }
  | {
      $and: FlagQuery[]
    }
  | {
      [slug: string]: boolean
    }

Example

// 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'],
    },
  },
}

FeatureContext

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.

Specification

interface FeatureContextValue<F extends DefaultFeature = DefaultFeature> {
  cache: Map<string, F>
}

<FeatureProvider />

Provider component that exposes the features in a more convenient way to get them by its own slugs.

Specification

interface FeatureProviderProps<F extends DefaultFeature = DefaultFeature> {
  features: F[]
  children: React.ReactNode
}

Example

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'),
  )
})

useFeature

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.

Specification

interface UseFeature<F extends DefaultFeature = DefaultFeature> {
  (slug: string): F | undefined
}

Example

// src/app.tsx
import { useFeature } from 'toggled'

function App() {
  const themeFF = useFeature('theme')

  return (
    <ThemeProvider theme={themeFF ? themeFF.settings.name : 'default'}>
      <Component />
    </ThemeProvider>
  )
}

useFlagQueryFn

Hook that is used to get the magic function that can process a flag query.

Specification

interface UseFlagQueryFn {
  (): (query: FlagQuery) => boolean
}

Example

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.

useFlag

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.

Specification

interface UseFlag {
  (query: FlagQuery): boolean
}

Example

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

<Flag />

Component to apply conditional rendering using a flagQuery

Specification

interface FlagProps {
  flagQuery: FlagQuery
  children: React.ReactNode
}

Example

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

<Feature />

Component to consume a feature object declaratively instead of useFeature

Specification

export interface FeatureProps {
  slug: string
  children(feature: DefaultFeature): React.ReactElement
}

Example

import { Feature } from 'toggled'

export default function App() {
  return (
    <Feature slug="chat">
      {feature => {
        return <ChatWidget settings={feature.settings}>
      }}
    </Feature>
  )
}

License

MIT © Ricardo Q. Bazan