launchdarkly / react-native-client-sdk

LaunchDarkly Client-side SDK for React Native
Other
47 stars 32 forks source link

Hooks for React Native #163

Closed vinaybedre closed 7 months ago

vinaybedre commented 1 year ago

Is your feature request related to a problem? Please describe. I would love to see the out of the box react like hooks in react native SDK, which simplifies the initialising of react native SDK and use feature flags in child components a breeze.

Describe the solution you'd like Introduce hooks for react native SDK, like we have in react web.

Describe alternatives you've considered None

louis-launchdarkly commented 1 year ago

Hello @vinaybedre, sorry for the late reply and thank you for the feature request. Hooks is something that we discussed internally and seems to make sense for React-based software, including this React Native SDK. There are some open issues we need to fix for React Native SDK first before we can look into adding features, but Hooks support is in our backlog.

ray-holland-es commented 1 year ago

Bump 👍

LucasEThomas commented 1 year ago

I made this reusable hook for my team's project. I made it yesterday so it might still have some problems, but with a little tweaking, I think most people should be able to reuse it in their projects. Hooks are the bread and butter of modern ergonomic React, something like this definitely should be in the sdk.

LaunchDarklyContext.tsx

// LaunchDarklyContext.tsx
import React, { ReactNode, createContext, useEffect, useState } from 'react';

import LDClient, {
  LDConfig,
  LDContext,
} from 'launchdarkly-react-native-client-sdk';

const LaunchDarklyContext = createContext<LDClient | undefined>(undefined);

let globalLdClient: LDClient | undefined;
/**
 * This is an escape hatch function in case you need to get feature flags outside of the React context.
 * Keep in mind, a well architected React app should not need to use this function very often.
 * If you find yourself using this a lot, consider your app's architecture. Perhaps it needs a refactor?
 * @returns a global reference to the launch darkly client singleton
 */
export function getLdClient() {
  return globalLdClient;
}

type Props = {
  children: ReactNode;
  context: LDContext;
  config: LDConfig;
};

/**
 * Wrap your app in this provider. Probably somewhere in your App.tsx you'll have something that looks like this:
 * ``` ts
 * return (
 *   <LaunchDarklyProvider context={context} config{config}>
 *     <TheRestOfYourAppGoesHere />
 *   </LaunchDarklyProvider>
 * );
 * ```
 */
export const LaunchDarklyProvider = ({ children, context, config }: Props) => {
  const [client, setClient] = useState<LDClient>();

  useEffect(() => {
    (async () => {
      const newClient = new LDClient();
      newClient.identify(context);
      newClient.configure(config, context);
      setClient(newClient);
      globalLdClient = newClient;
    })();
  }, [context, config]);

  return (
    <LaunchDarklyContext.Provider value={client}>
      {children}
    </LaunchDarklyContext.Provider>
  );
};

export default LaunchDarklyContext;

useFeatureFlag.ts

// useFeatureFlag.ts
import { useState, useEffect, useContext } from 'react';
import LaunchDarklyContext from './LaunchDarklyContext';
import LDClient from 'launchdarkly-react-native-client-sdk';

// using js contructors as the type enum so that we can use ReturnType<T> on them and make the hook strongly typed
type LaunchDarklyFlagValConstructor =
  | StringConstructor
  | BooleanConstructor
  | NumberConstructor
  | ObjectConstructor;
/**
 * call the hook like this:
 * ```ts
 * const myBoolFeatureFlag = useFeatureFlag(Boolean, 'key-goes-here', false)
 * ```
 */
export default <T extends LaunchDarklyFlagValConstructor>(
  type: T,
  key: string,
  defaultVal: ReturnType<T>
) => {
  const [flag, setFlag] = useState(defaultVal);
  const ldClient = useContext(LaunchDarklyContext);

  useEffect(() => {
    if (!ldClient) return;
    const getFeatureFlag = getGetFeatureFlag(ldClient);
    const listener = async (updatedKey: string) => {
      const newFlag = await getFeatureFlag(type, updatedKey, defaultVal);
      setFlag(newFlag ?? defaultVal);
    };
    // call the listener at the beginning because the launch darkly listener doesn't do this for us
    listener(key);
    // register the launch darkly flag listener
    ldClient?.registerFeatureFlagListener(key, listener);
    return () => ldClient?.unregisterFeatureFlagListener(key, listener);
  }, [ldClient]);
  return flag;
};

const getGetFeatureFlag =
  (ldClient: LDClient) =>
  async <T extends LaunchDarklyFlagValConstructor>(
    type: LaunchDarklyFlagValConstructor,
    key: string,
    defaultVal: ReturnType<T>
  ) =>
    type === Boolean
      ? await ldClient?.boolVariation(key, defaultVal)
      : type === Number
      ? await ldClient?.numberVariation(key, defaultVal)
      : type === String
      ? await ldClient?.stringVariation(key, defaultVal)
      : type === Object
      ? JSON.parse(await ldClient?.jsonVariation(key, defaultVal))
      : undefined;

ExampleUsageComponent.tsx

import React from 'react';
import useFeatureFlag from '../../hooks/useFeatureFlag';
import { Text } from 'react-native';

export const ExampleComponent = () => {
  // The return types of useFeatureFlag are strongly typed, try it out!
  const darklyText = useFeatureFlag(String, 'darkly-text', 'Fancy Text!');
  const enableFancyFeature = useFeatureFlag(Boolean, 'fancy-feature', false);
  return (
    <>
      <Text>Example component</Text>
      <Text>{DarklyText}</Text>
      {enableFancyFeature && <Text>Fancy Feature Enabled!</Text>}
    </>
  );
};
nazmeln commented 10 months ago

Hey @louis-launchdarkly, Any updates on this feature? I see it's been in the backlog for over a year now 🥲

LucasEThomas commented 10 months ago

I created an npm package that wraps LD and gives you hooks and a bunch of other nice things. It makes this library extremely simple to use.

My team has been using it in our production Android app for a few months now so it's starting to feel mature to me. I haven't tested it in ios, but it's entirely TS code so it should work. Try it out! I welcome feedback.

https://www.npmjs.com/package/launch-darkly-react-native-client-factory

yusinto commented 9 months ago

The react-native SDK has been re-written and released. The new version v10 is purely in TypeScript and supports Expo. Hooks included. Please consider upgrading to that new version. Thank you for your patience.

github-actions[bot] commented 8 months ago

This issue is marked as stale because it has been open for 30 days without activity. Remove the stale label or comment, or this will be closed in 7 days.