Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

flag

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

flag - npm Package Compare versions

Comparing version 4.4.0 to 5.0.0-alpha.0

.github/workflows/test.yml

54

package.json
{
"name": "flag",
"version": "4.4.0",
"description": "Feature flagging made easy for React and Redux",
"main": "build/index.js",
"types": "build/index.d.ts",
"version": "5.0.0-alpha.0",
"description": "Strictly typed feature flagging for React",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"sideEffects": false,
"repository": "https://github.com/garbles/flag",

@@ -11,40 +13,24 @@ "author": "Gabe Scholz",

"devDependencies": {
"@types/enzyme": "^3.9.1",
"@types/invariant": "^2.2.30",
"@types/jest": "^24.0.11",
"@types/lodash": "^4.14.123",
"@types/react": "^16.8.12",
"@types/react-dom": "^16.8.3",
"@types/react-redux": "^7.0.7",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"jest": "^24.7.1",
"np": "^4.0.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.0.2",
"ts-jest": "^24.0.1",
"typescript": "^3.5.3"
"@testing-library/react": "^13.0.0",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.23",
"jest": "^27.5.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"ts-jest": "^27.1.4",
"tsup": "^5.12.5",
"typescript": "^4.6.3"
},
"dependencies": {
"deep-computed": "^0.2.0",
"invariant": "^2.2.4",
"lodash": "^4.17.11",
"useful-types": "^0.4.0"
"@types/react": "^18.0.5",
"async-ref": "^0.1.6"
},
"peerDependencies": {
"@types/react": "^16.8.0",
"@types/react-dom": "^16.8.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-redux": "^5.0.0 || ^6.0.0 || ^7.0.0",
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0"
"react": "^18"
},
"scripts": {
"clean": "rm -rf build",
"build": "tsc",
"prepublish": "yarn clean && yarn build",
"test": "jest",
"pub": "np"
"build": "tsup",
"prepublishOnly": "jest && tsc && tsup"
}
}
# Flag
_Feature flagging made easy for React and Redux_
:caution: v5 is a work in progress and is not yet published. :caution:
This library aims to offer a best-in-class interface for working with feature flags in TypeScript-based React applications.
```
yarn add flag
npm install flag
```
## Motivation
Feature flagging is necessary for large client-side applications. They improve development speed
and allow teams to test new features before they are stable. In order to **WANT** to use feature
flags in an application, they should be **VERY** easy to add and remove. That means minimal
boiler plate and no need to pass boolean props down through component hierarchy. Such a thing could be
done with global variables; however, they live outside of the React/Redux lifecycle, making them
more difficult to control. Instead, this library injects and then accesses feature flags directly
from the React context without getting in your way.
Flag allows you to declare flags as either plain values or as functions. If a flag is a function then it is referred to as a computed flag. The function accepts one argument which is the flags object itself. You do not have to use computed flags, but they can be very convenient. For example,
```ts
const flags = {
// properties can be nested objects
features: {
// they can be boolean
useMyCoolNewThing: true
},
config: {
// they can be strings
apiUrl: "www.example.com/api"
},
// they can be numbers
cool: 1,
dude: 5,
// they can be computed
coolAndDude: flags => flags.cool + flags.dude,
// they can be computed from other computed properties.
// other computed properties are resolved for you, so that you do not
// need to call it as a function.
largeCoolAndDude: flags => flags.coolAndDude > 10
};
```
## Getting Started
This library has strong TypeScript support as of v4. In order to get that support, you must
initialize the `flag` library before using it.
`flag` works by creating bindings at runtime so that context providers, components, and hooks are all strictly typed together. `createFlags` builds these bindings without requiring an data.
### createFlags
Creates React bindings for flags. **You should only initialize one instance of this API**. Does
not take any value arguments, but takes one type argument `T` which is the shape of your resolved
flags.
```ts
// flags.ts
import createFlags from "flag";
import { createFlags } from "flag";

@@ -73,81 +33,31 @@ export type MyFlags = {

const { FlagsProvider, Flag, useFlag, useFlags } = createFlags<MyFlags>();
export { FlagsProvider, Flag, useFlag, useFlags };
export const { FlagBackendProvider, Flag, useFlag } = createFlags<MyFlags>();
```
### createReduxBindings
## React Bindings
You can also add support for Redux by importing `createReduxBindings` from `flag/redux`.
### FlagBackendProvider
| Args | Type | Required | Description |
| ---------- | ------------------ | -------- | --------------------------------- |
| `provider` | `FlagsProvider<T>` | `true` | Provider created by `createFlags` |
_Returned as part of `createFlags<T>()`._
```ts
// flags.ts
This React component provides a `Backend<T>` (see below) as a data source for `Flag` and `useFlag`.
// ... the above
| Props | Type | Required | Description |
| ---------- | ------------------ | -------- | ---------------------- |
| `backend` | `Types.Backend<T>` | `true` | All pre-computed flags |
| `children` | `ReactNode` | `true` | React children |
import createReduxBindings from "flag/redux";
const {
setFlagsAction,
getFlagsSelector,
createFlagsReducer,
ConnectedFlagsProvider
} = createReduxBindings(FlagsProvider);
export {
setFlagsAction,
getFlagsSelector,
createFlagsReducer,
ConnectedFlagsProvider
};
```
## React API
For brevity, the type `T` in the section below refers to the shape of your resolved feature flags.
### Computable
Generic type used to describe unresolved flags. Very useful when including functions are part of your flag definitions because function arguments can be inferred.
```tsx
import { Computable } from "flag";
type MyFlags = {
a: boolean;
b: boolean;
c: boolean;
};
const flags: Computable<MyFlags> = {
a: true,
b: false,
// 👇 `flags` type checks!
c: flags => flags.a && flags.b
};
```
### FlagsProvider
Returned as part of `createFlags()`. React component that makes flags available to children through the Context API.
| Props | Type | Required | Description |
| ---------- | --------------- | -------- | ---------------------- |
| `flags` | `Computable<T>` | `true` | All pre-computed flags |
| `children` | `ReactNode` | `true` | React children |
```tsx
// index.tsx
import { NullBackend } from "flag";
import { MyApplication } from "./app";
import { FlagsProvider, Flag } from "./flags";
import { FlagBackendProvider } from "./flags";
const backend = new NullBackend();
const instance = (
<FlagsProvider flags={flags}>
<FlagBackendProvider backend={backend}>
<MyApplication />
</FlagsProvider>
</FlagBackendProvider>
);

@@ -158,67 +68,26 @@

### Flag
### useFlag
Returned as part of `createFlags()`. Renders a some UI based on whether a flag is truthy or falsy. It's a glorified if statement 😬. Must be used in side of `FlagsProvider`.
_Returned as part of `createFlags<T>()`._
| Props | Type | Required | Description |
| ------------------- | ----------------------------- | -------- | ----------------------------------- |
| `name` | `string[]` | `true` | Must be a valid key path of `T` |
| `children` | `ReactNode` | `false` | React children |
| `render` | `(flags: T) => ReactNode` | `false` | Function that returns a `ReactNode` |
| `fallbackRender` | `(flags: T) => ReactNode` | `false` | Function that returns a `ReactNode` |
| `component` | `ComponentType<{ flags: T }>` | `false` | React Component with `T` as props |
| `fallbackComponent` | `ComponentType<{ flags: T }>` | `false` | React Component with `T` as props |
A hook to fetch a single flag. Requires a valid key path and a default value. The key path must terminate at a string, boolean or number and the default value must be of the same type that it terminates. Forcing a default to be provided will minimize the change of a runtime error occurring.
Order of deciding which of these nodes to renders is as follows:
| Args | Type | Required | Description |
| -------------- | -------------------------------------------- | -------- | ---------------------------------------------------------- |
| `keyPath` | `Types.KeyPath<T> \| Types.KeyPathString<T>` | `true` | A valid key path of `T` to a string, boolean or number |
| `defaultValue` | `GetValueFromKeyPath<T, KP>` | `true` | A fallback in case it is not available in the `Backend<T>` |
- If the flag is `truthy`:
- render `children` if defined
- call `render` with `T` if defined _or_
- call `component` with `{flags: T}` if defined _else_
- return `null`
- If the flag is `falsy`:
- call `fallbackRender` with `T` if defined _or_
- call `fallbackComponent` with `{ flags: T }` if defined _else_
- return `null`
```tsx
<Flag
name={["features", "useMyCoolNewThing"]}
render={() => <div>Rendered on truthy</div>}
fallbackRender={() => <div>Rendered on falsy</div>}
/>
```
### useFlags
Returned as part of `createFlags()`. A React hook that returns all of the flags. Must be used in side of `FlagsProvider`.
```tsx
// my-component.tsx
import { useFlags } from "./flags";
import { useFlag } from "./flags";
const MyComponent = () => {
const flags = useFlags();
/**
* The key path can be either an array or string of keys joined by `.`
* It _must_ terminate at a string, boolean or number type.
*/
const apiUrl = useFlag(["config", "apiUrl"], "https://example.com");
const apiUrl2 = useFlag("config.apiUrl", "https://example.com");
return <div>The API url is "{flags.config.apiUrl}"</div>;
};
```
### useFlag
Returned as part of `createFlags()`. A React hook to return a single flag. Must be used in side of `FlagsProvider`.
| Args | Type | Required | Description |
| --------- | ---------- | -------- | ------------------------------- |
| `keyPath` | `string[]` | `true` | Must be a valid key path of `T` |
```tsx
// my-component.tsx
import { useFlags } from "./flags";
const MyComponent = () => {
const apiUrl = useFlag(["config", "apiUrl"]);
return <div>The API url is "{apiUrl}"</div>;

@@ -228,94 +97,30 @@ };

## Redux API
### Flag
### createFlagsReducer
_Returned as part of `createFlags<T>()`._
Returned as part of `createReduxBindings(...)`. Creates a reducer to be used in your Redux stores.
Renders a some UI based on whether a flag is `false` or not. (It's a glorified if statement 😬).
| Args | Type | Required | Description |
| ------- | ---- | -------- | ------------------------------- |
| `flags` | `T` | `true` | The initial value of your flags |
| Args | Type | Required | Description |
| -------------- | -------------------------------------------- | -------- | ---------------------------------------------------------- |
| `keyPath` | `Types.KeyPath<T> \| Types.KeyPathString<T>` | `true` | A valid key path of `T` to a string, boolean or number |
| `defaultValue` | `GetValueFromKeyPath<T, KP>` | `true` | A fallback in case it is not available in the `Backend<T>` |
| `render` | `(flags: T) => ReactNode` | `true` | Function that returns a `ReactNode` |
| `fallback` | `() => ReactNode` | `false` | Function that returns a `ReactNode` |
```tsx
// reducer.ts
import { combineReducers } from "redux";
import { Computable } from "flag";
import { createFlagsReducer, MyFlags } from "./flags";
import { otherReducer } from "./other-reducer";
const flags: Computable<MyFlags> = {
// ...
};
export default combineReducers({
// 👇 must use the "flags" key of your state
flags: createFlagsReducer(flags),
other: otherReducer
});
<Flag
name="features.useMyCoolNewThing"
defaultValue={false}
render={() => <div>Rendered on truthy</div>}
fallback={() => <div>Rendered on falsy</div>}
/>
```
### getFlagsSelector
## Backends
A selector to retrieve _computed_ flags from Redux state. It is not enough to say `state.flags` because `createFlagsReducer` does not eagerly evaluate computable flags.
(Though I suppose if you don't use any computable flags, then you don't necessarily need this 🤷‍♂️.)
## Setting NODE_ENV
```tsx
// reducer.ts
import { getFlagsSelector, MyFlags } from "./flags";
type State = {
flags: MyFlags;
// ...
};
// ...
export const getFlags = (state: State) => getFlagsSelector(state);
```
### ConnectedFlagsProvider
Returned as part of `createReduxBindings(...)`. Wraps `FlagsProvider`, fetching the flags from Redux state.
```tsx
import { Provider } from "redux";
import { MyApplication } from "./app";
import { ConnectedFlagsProvider } from "./flags";
import { store } from "./store";
const instance = (
<Provider store={store}>
<ConnectedFlagsProvider>
<MyApplication />
</ConnectedFlagsProvider>
</Provider>
);
React.render(instance, document.querySelector("#app"));
```
### setFlagsAction
Returned as part of `createReduxBindings(...)`. A dispatchable action that sets flags. Merges a partial value of pre-computed flags with existing pre-computed flags.
| Args | Type | Required | Description |
| ------- | --------------- | -------- | -------------------------- |
| `flags` | `Computable<T>` | `true` | Partial pre-computed flags |
```tsx
import { Thunk } from "redux-thunk";
import { setFlagsAction } from "./flags";
export const someThunk: Thunk<any> = ({ dispatch }) => async () => {
const user = await fetchUser();
dispatch(setFlagsAction(user.flags));
// ...
};
```
## License
MPL-2.0

@@ -1,5 +0,24 @@

import { createFlags } from "./create-flags";
import type { AsyncMutableRefObject } from "async-ref";
import type { KeyPath, KeyPathString, GetValueFromKeyPath, ExternalStore, GetValueFromKeyPathString } from "./types";
import type { Backend } from "./backends";
export { Computable } from "deep-computed";
export { createFlags };
export default createFlags;
type KeyPath_<T> = KeyPath<T>;
type KeyPathString_<T> = KeyPathString<T>;
type GetValueFromKeyPath_<T, KP extends KeyPath<T>> = GetValueFromKeyPath<T, KP>;
type GetValueFromKeyPathString_<T, KP extends KeyPathString<T>> = GetValueFromKeyPathString<T, KP>;
type ExternalStore_<T> = ExternalStore<T>;
type Backend_<T> = Backend<T>;
type AsyncMutableRefObject_<T> = AsyncMutableRefObject<T>;
export module Types {
export type KeyPath<T> = KeyPath_<T>;
export type KeyPathString<T> = KeyPathString_<T>;
export type GetValueFromKeyPath<T, KP extends KeyPath<T>> = GetValueFromKeyPath_<T, KP>;
export type GetValueFromKeyPathString<T, KP extends KeyPathString<T>> = GetValueFromKeyPathString_<T, KP>;
export type ExternalStore<T> = ExternalStore_<T>;
export type Backend<T> = Backend_<T>;
export type AsyncMutableRefObject<T> = AsyncMutableRefObject_<T>;
}
export { createFlags } from "./create-flags";
export { AlwaysBackend, AbstractBackend, ComputedBackend, NullBackend, StaticBackend } from "./backends";

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc