Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Provider and hooks #306

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
],
'import/default': 'error',
'import/export': 'error',
'import/extensions': ['error', 'never', { json: 'always' }],
'import/no-self-import': 'error',
'import/no-cycle': 'error',
'import/no-useless-path-segments': 'error',
Expand Down
45 changes: 9 additions & 36 deletions packages/sdk/react-native/example/App.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,17 @@
import { CLIENT_SIDE_SDK_KEY } from '@env';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';

import { init, type LDClientImpl } from '@launchdarkly/react-native-client-sdk';
import { LDProvider } from '@launchdarkly/react-native-client-sdk';

const context = { kind: 'user', key: 'test-user-1' };

export default function App() {
const [ldc, setLdc] = useState<LDClientImpl>();
const [flag, setFlag] = useState<boolean>(false);
import Welcome from './src/welcome';

useEffect(() => {
init(CLIENT_SIDE_SDK_KEY, context)
.then((c) => {
setLdc(c);
})
.catch((e) => console.log(e));
}, []);

useEffect(() => {
const f = ldc?.boolVariation('dev-test-flag', false);
setFlag(f ?? false);
}, [ldc]);
const context = { kind: 'user', key: 'test-user-1' };

const App = () => {
return (
<View style={styles.container}>
<Text>{flag ? <>devTestFlag: {`${flag}`}</> : <>loading...</>}</Text>
</View>
<LDProvider clientSideSdkKey={CLIENT_SIDE_SDK_KEY} context={context}>
<Welcome />
</LDProvider>
);
}
};

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
});
export default App;
27 changes: 27 additions & 0 deletions packages/sdk/react-native/example/src/welcome.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { StyleSheet, Text, View } from 'react-native';

import { useBoolVariation } from '@launchdarkly/react-native-client-sdk';

export default function Welcome() {
const flag = useBoolVariation('dev-test-flag', false);

return (
<View style={styles.container}>
<Text>Welcome to LaunchDarkly</Text>
<Text>{flag ? <>devTestFlag: {`${flag}`}</> : <>loading...</>}</Text>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
});
16 changes: 11 additions & 5 deletions packages/sdk/react-native/link-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ do
mkdir -p "$COMMON_DIR"
mkdir -p "$CLIENT_COMMON_DIR"

rsync -av dist "$SDK_DIR"
rsync -aq src "$SDK_DIR"
rsync -aq package.json "$SDK_DIR"
rsync -aq LICENSE "$SDK_DIR"
rsync -aq node_modules "$SDK_DIR"
rsync -aq src "$SDK_DIR"
rsync -av dist "$SDK_DIR"
rsync -aq node_modules/base64-js "$SDK_DIR"/node_modules
rsync -aq node_modules/event-target-shim "$SDK_DIR"/node_modules

rsync -aq ../../shared/common/dist "$COMMON_DIR"
rsync -aq ../../shared/common/src "$COMMON_DIR"
rsync -aq ../../shared/common/package.json "$COMMON_DIR"

rsync -aq ../../shared/common/ "$COMMON_DIR"
rm -rf "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/ "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/dist "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/src "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/package.json "$CLIENT_COMMON_DIR"
done
5 changes: 5 additions & 0 deletions packages/sdk/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"ios": "yarn ./example && yarn build && yarn ./example ios"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
Expand All @@ -50,6 +51,8 @@
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
"@types/jest": "^29.5.0",
"@types/react": "^18.2.31",
"@types/react-native": "^0.72.5",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"eslint": "^8.45.0",
Expand All @@ -61,6 +64,8 @@
"jest": "^29.5.0",
"launchdarkly-js-test-helpers": "^2.2.0",
"prettier": "^3.0.0",
"react": "^18.2.0",
"react-native": "^0.72.6",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.0",
"typedoc": "0.25.0",
Expand Down
20 changes: 20 additions & 0 deletions packages/sdk/react-native/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useBoolVariation, useBoolVariationDetail } from './useBoolVariation';
import { useJsonVariation, useJsonVariationDetail } from './useJsonVariation';
import useLDClient from './useLDClient';
import { useNumberVariation, useNumberVariationDetail } from './useNumberVariation';
import { useStringVariation, useStringVariationDetail } from './useStringVariation';
import { useVariation, useVariationDetail } from './useVariation';

export {
useLDClient,
useVariation,
useVariationDetail,
useNumberVariation,
useNumberVariationDetail,
useBoolVariation,
useBoolVariationDetail,
useStringVariation,
useStringVariationDetail,
useJsonVariationDetail,
useJsonVariation,
};
13 changes: 13 additions & 0 deletions packages/sdk/react-native/src/hooks/useBoolVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import useLDClient from './useLDClient';

export const useBoolVariation = (flagKey: string, defaultValue: boolean) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these will be very nice for consistent experiment results. (Aside from also allow type safety, which is nice.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Thank you for pushing for these!

const ldClient = useLDClient();
return ldClient?.boolVariation(flagKey, defaultValue) ?? defaultValue;
};

export const useBoolVariationDetail = (flagKey: string, defaultValue: boolean) => {
const ldClient = useLDClient();
return ldClient?.boolVariationDetail(flagKey, defaultValue) ?? defaultValue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because detailed variations return objects we need to ensure we keep stable object references. Which may happen for flags that exist, because we might be returning a value we have stored.

I am concerned that the variation for a default value will not be stable. Which could lead to the hook triggering more re-renders than desired.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this is definitely a bug. What do you think of a fix like this:

Suggested change
return ldClient?.boolVariationDetail(flagKey, defaultValue) ?? defaultValue;
const def = {
value: defaultValue,
variationIndex: null,
reason: null,
};
return ldClient?.boolVariationDetail(flagKey, defaultValue) ?? def;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this fixes a case where the ldclient isn't initialized to have a consistent return.

It doesn't affect the stability of the reference returned by the hook. I think you would need to make a memo.

};

export default useBoolVariation;
13 changes: 13 additions & 0 deletions packages/sdk/react-native/src/hooks/useJsonVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import useLDClient from './useLDClient';

export const useJsonVariation = (flagKey: string, defaultValue: unknown) => {
const ldClient = useLDClient();
return ldClient?.jsonVariation(flagKey, defaultValue) ?? defaultValue;
};

export const useJsonVariationDetail = (flagKey: string, defaultValue: unknown) => {
const ldClient = useLDClient();
return ldClient?.jsonVariationDetail(flagKey, defaultValue) ?? defaultValue;
};

export default useJsonVariation;
10 changes: 10 additions & 0 deletions packages/sdk/react-native/src/hooks/useLDClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useContext } from 'react';

import { context, ReactSdkContext } from '../provider/reactSdkContext';

const useLDClient = () => {
const { ldClient } = useContext<ReactSdkContext>(context);
return ldClient;
};

export default useLDClient;
13 changes: 13 additions & 0 deletions packages/sdk/react-native/src/hooks/useNumberVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import useLDClient from './useLDClient';

export const useNumberVariation = (flagKey: string, defaultValue: number) => {
const ldClient = useLDClient();
return ldClient?.numberVariation(flagKey, defaultValue) ?? defaultValue;
};

export const useNumberVariationDetail = (flagKey: string, defaultValue: number) => {
const ldClient = useLDClient();
return ldClient?.numberVariationDetail(flagKey, defaultValue) ?? defaultValue;
};

export default useNumberVariation;
13 changes: 13 additions & 0 deletions packages/sdk/react-native/src/hooks/useStringVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import useLDClient from './useLDClient';

export const useStringVariation = (flagKey: string, defaultValue: string) => {
const ldClient = useLDClient();
return ldClient?.stringVariation(flagKey, defaultValue) ?? defaultValue;
};

export const useStringVariationDetail = (flagKey: string, defaultValue: string) => {
const ldClient = useLDClient();
return ldClient?.stringVariationDetail(flagKey, defaultValue) ?? defaultValue;
};

export default useStringVariation;
15 changes: 15 additions & 0 deletions packages/sdk/react-native/src/hooks/useVariation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { LDFlagValue } from '@launchdarkly/js-client-sdk-common';

import useLDClient from './useLDClient';

export const useVariation = (flagKey: string, defaultValue?: any): LDFlagValue => {
const ldClient = useLDClient();
return ldClient?.variation(flagKey, defaultValue) ?? defaultValue;
};

export const useVariationDetail = (flagKey: string, defaultValue?: any): LDFlagValue => {
const ldClient = useLDClient();
return ldClient?.variationDetail(flagKey, defaultValue) ?? defaultValue;
};

export default useVariation;
32 changes: 29 additions & 3 deletions packages/sdk/react-native/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
/**
* This is the API reference for the React Native LaunchDarkly SDK.
*
* TODO:
* TODO: add rn sdk api docs
*
* For more information, see the SDK reference guide.
*
* @packageDocumentation
*/
import init from './init';
import {
useBoolVariation,
useBoolVariationDetail,
useJsonVariation,
useJsonVariationDetail,
useLDClient,
useNumberVariation,
useNumberVariationDetail,
useStringVariation,
useStringVariationDetail,
useVariation,
useVariationDetail,
} from './hooks';
import { setupPolyfill } from './polyfills';
import { LDProvider } from './provider';

setupPolyfill();

export * from '@launchdarkly/js-client-sdk-common';

export { init };
export {
LDProvider,
useLDClient,
useVariation,
useVariationDetail,
useNumberVariation,
useNumberVariationDetail,
useBoolVariation,
useBoolVariationDetail,
useStringVariation,
useStringVariationDetail,
useJsonVariationDetail,
useJsonVariation,
};
30 changes: 30 additions & 0 deletions packages/sdk/react-native/src/provider/LDProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { PropsWithChildren, useEffect, useState } from 'react';

import { LDClient, LDContext, LDOptions } from '@launchdarkly/js-client-sdk-common';

import init from '../init';
import { Provider, ReactSdkContext } from './reactSdkContext';

type LDProps = {
clientSideSdkKey: string;
context: LDContext;
options?: LDOptions;
};
const LDProvider = ({
clientSideSdkKey,
context,
options,
children,
}: PropsWithChildren<LDProps>) => {
const [value, setValue] = useState<ReactSdkContext>({});

useEffect(() => {
init(clientSideSdkKey, context, options).then((ldClient: LDClient) => {
setValue({ allFlags: ldClient.allFlags(), ldClient });
});
}, []);

return <Provider value={value}>{children}</Provider>;
};

export default LDProvider;
4 changes: 4 additions & 0 deletions packages/sdk/react-native/src/provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import LDProvider from './LDProvider';

// eslint-disable-next-line import/prefer-default-export
export { LDProvider };
14 changes: 14 additions & 0 deletions packages/sdk/react-native/src/provider/reactSdkContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext } from 'react';

import { LDClient, LDFlagSet } from '@launchdarkly/js-client-sdk-common';

type ReactSdkContext = {
allFlags?: LDFlagSet;
ldClient?: LDClient;
};
Comment on lines +5 to +8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still in flux and will change to have more/less things exposed.


const context = createContext<ReactSdkContext>({});

const { Provider, Consumer } = context;

export { context, Provider, Consumer, ReactSdkContext };
Loading