launchdarkly / react-client-sdk

LaunchDarkly Client-side SDK for React.js
Other
79 stars 67 forks source link

Allow users to create type-safe/strictly typed feature flags with useFlags #151

Open ewlsh opened 1 year ago

ewlsh commented 1 year ago

Requirements

Related issues

139, https://github.com/launchdarkly/js-sdk-common/issues/32

Describe the solution you've provided

This MR updates useFlags generics and definition to allow implementing codebases to specify their own strictly typed feature flag interface.

// Before
declare const useFlags: <T extends LDFlagSet = LDFlagSet>() => T;

// After
declare function useFlags<T extends Record<string, any> = LDFlagSet>(): T;

By declaring useFlags as const and not a function it is not possible to overload its definition. Function overloads allow implementing codebases to re-declare useFlags to be more strict.

Additionally, currently the generic on useFlags is set to extends LDFlagSet, but this is not necessarily true. When useCamelCaseFlagKeys is true, the return value from useFlags can differ from LDFlagSet if LDFlagSet has been augmented for the client.

Describe alternatives you've considered

I also considered introducing a second interface, ReactLDFlagSet (or similar), which did not include an index type but this would not be backwards compatible and it seems like the goal is to have strict typing be opt-in.

Open to other alternatives, in our codebase we've considered writing a wrapping function.

Additional context

Example codebase implementation using these changes:

declare module 'launchdarkly-js-sdk-common' {
    export interface LDFlagSet {
        'show-a-cool-feature': boolean;
        'demonstrate-another-cool-number': number;
    }
}

const ldClient = initialize(
    getClientSideID(),
    { anonymous: true },
    {
        allAttributesPrivate: true,
        sendEvents: true,
    },
);

// ldClient.allFlags()['demonstrate-another-cool-number']

export interface CamelCaseFeatureFlags {
    showACoolFeature: boolean;
    demonstrateAnotherCoolNumber: number;
}

declare module 'launchdarkly-react-client-sdk' {
    export function useFlags(): CamelCaseFeatureFlags;
}

export const LaunchDarklyProvider: React.FC<unknown> = props => {
    return (
        <LDProvider
            clientSideID={getClientSideID()}
            ldClient={ldClient}
            reactOptions={{
                useCamelCaseFlagKeys: true,
            }}
            options={{
                // bootstrap: defaultFeatureFlags,
            }}
        >
            {props.children}
        </LDProvider>
    );
};
louis-launchdarkly commented 1 year ago

Hello @ewlsh, thank you for the contribution! We will discuss the change and give you a reply after that.

ewlsh commented 2 months ago

@yusinto this is updated.