Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { render } from '@testing-library/react';
import React from 'react';

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

import { useLDClient } from '../../src/hooks';
import LDProvider from '../../src/provider/LDProvider';
import setupListeners from '../../src/provider/setupListeners';
import ReactNativeLDClient from '../../src/ReactNativeLDClient';

jest.mock('../../src/ReactNativeLDClient');
jest.mock('../../src/provider/setupListeners');

const TestApp = () => {
const ldClient = useLDClient();
Expand All @@ -22,7 +21,6 @@ const TestApp = () => {
};
describe('LDProvider', () => {
let ldc: ReactNativeLDClient;
const mockSetupListeners = setupListeners as jest.Mock;

beforeEach(() => {
jest.useFakeTimers();
Expand All @@ -45,9 +43,7 @@ describe('LDProvider', () => {
};
},
);
mockSetupListeners.mockImplementation((client: ReactNativeLDClient, setState: any) => {
setState({ client });
});

ldc = new ReactNativeLDClient('mobile-key', AutoEnvAttributes.Enabled);
});

Expand Down

This file was deleted.

160 changes: 108 additions & 52 deletions packages/sdk/react-native/src/hooks/variation/useTypedVariation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
import { useEffect, useRef, useState } from 'react';

import type ReactNativeLDClient from '../../ReactNativeLDClient';
import useLDClient from '../useLDClient';
import { LDEvaluationDetailTyped } from './LDEvaluationDetail';

function getTypedVariation<T extends boolean | number | string | unknown>(
ldClient: ReactNativeLDClient,
key: string,
defaultValue: T,
): T {
switch (typeof defaultValue) {
case 'boolean':
return ldClient.boolVariation(key, defaultValue as boolean) as T;
case 'number':
return ldClient.numberVariation(key, defaultValue as number) as T;
case 'string':
return ldClient.stringVariation(key, defaultValue as string) as T;
case 'undefined':
case 'object':
return ldClient.jsonVariation(key, defaultValue) as T;
default:
return ldClient.variation(key, defaultValue);
}
}

/**
* Determines the strongly typed variation of a feature flag.
*
Expand All @@ -15,21 +38,72 @@ export const useTypedVariation = <T extends boolean | number | string | unknown>
defaultValue: T,
): T => {
const ldClient = useLDClient();
const [value, setValue] = useState<T>(() =>
ldClient ? getTypedVariation(ldClient, key, defaultValue) : defaultValue,
);
const valueRef = useRef<T>(value);

useEffect(() => {
valueRef.current = value;
}, [value]);

useEffect(() => {
setValue(getTypedVariation(ldClient, key, defaultValue));
const handleChange = (): void => {
const newValue = getTypedVariation(ldClient, key, defaultValue);
if (newValue !== valueRef.current) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Object types will still run into problems since they will not test equal, but I think it is still an improvement from before.

We can look into doing a deeper compare

setValue(newValue);
}
};
ldClient.on('change', handleChange);
return () => {
ldClient.off('change', handleChange);
};
}, [key]);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe we would want to reregister if defaultValue changes?


return value;
};

function getTypedVariationDetail<T extends boolean | number | string | unknown>(
ldClient: ReactNativeLDClient,
key: string,
defaultValue: T,
): LDEvaluationDetailTyped<T> {
let detail: LDEvaluationDetailTyped<T>;
switch (typeof defaultValue) {
case 'boolean':
return ldClient.boolVariation(key, defaultValue as boolean) as T;
case 'number':
return ldClient.numberVariation(key, defaultValue as number) as T;
case 'string':
return ldClient.stringVariation(key, defaultValue as string) as T;
case 'boolean': {
detail = ldClient.boolVariationDetail(
key,
defaultValue as boolean,
) as LDEvaluationDetailTyped<T>;
break;
}
case 'number': {
detail = ldClient.numberVariationDetail(
key,
defaultValue as number,
) as LDEvaluationDetailTyped<T>;
break;
}
case 'string': {
detail = ldClient.stringVariationDetail(
key,
defaultValue as string,
) as LDEvaluationDetailTyped<T>;
break;
}
case 'undefined':
case 'object':
return ldClient.jsonVariation(key, defaultValue) as T;
default:
return ldClient.variation(key, defaultValue);
case 'object': {
detail = ldClient.jsonVariationDetail(key, defaultValue) as LDEvaluationDetailTyped<T>;
break;
}
default: {
detail = ldClient.variationDetail(key, defaultValue) as LDEvaluationDetailTyped<T>;
break;
}
}
};
return { ...detail, reason: detail.reason ?? null };
}

/**
* Determines the strongly typed variation of a feature flag for a context, along with information about
Expand All @@ -55,48 +129,30 @@ export const useTypedVariationDetail = <T extends boolean | number | string | un
defaultValue: T,
): LDEvaluationDetailTyped<T> => {
const ldClient = useLDClient();
const [detail, setDetail] = useState<LDEvaluationDetailTyped<T>>(() =>
ldClient
? getTypedVariationDetail(ldClient, key, defaultValue)
: { value: defaultValue, reason: null },
);
const detailRef = useRef<LDEvaluationDetailTyped<T>>(detail);

switch (typeof defaultValue) {
case 'boolean': {
const detail = ldClient.boolVariationDetail(key, defaultValue as boolean);

return {
...detail,
reason: detail.reason ?? null,
} as LDEvaluationDetailTyped<T>;
}
case 'number': {
const detail = ldClient.numberVariationDetail(key, defaultValue as number);
useEffect(() => {
detailRef.current = detail;
}, [detail]);

return {
...detail,
reason: detail.reason ?? null,
} as LDEvaluationDetailTyped<T>;
}
case 'string': {
const detail = ldClient.stringVariationDetail(key, defaultValue as string);
useEffect(() => {
setDetail(getTypedVariationDetail(ldClient, key, defaultValue));
const handleChange = () => {
const newDetail = getTypedVariationDetail(ldClient, key, defaultValue);
if (newDetail.value !== detailRef.current.value) {
setDetail(newDetail);
}
};
ldClient.on('change', handleChange);
return () => {
ldClient.off('change', handleChange);
};
}, [key]);

return {
...detail,
reason: detail.reason ?? null,
} as LDEvaluationDetailTyped<T>;
}
case 'undefined':
case 'object': {
const detail = ldClient.jsonVariationDetail(key, defaultValue);

return {
...detail,
reason: detail.reason ?? null,
} as LDEvaluationDetailTyped<T>;
}
default: {
const detail = ldClient.variationDetail(key, defaultValue);

return {
...detail,
reason: detail.reason ?? null,
} as LDEvaluationDetailTyped<T>;
}
}
return detail;
};
16 changes: 7 additions & 9 deletions packages/sdk/react-native/src/provider/LDProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { PropsWithChildren, useMemo } from 'react';

import ReactNativeLDClient from '../ReactNativeLDClient';
import { Provider, ReactContext } from './reactContext';
import setupListeners from './setupListeners';
import { Provider } from './reactContext';

type LDProps = {
client: ReactNativeLDClient;
Expand All @@ -19,13 +18,12 @@ type LDProps = {
* @constructor
*/
const LDProvider = ({ client, children }: PropsWithChildren<LDProps>) => {
const [state, setState] = useState<ReactContext>({ client });
// NOTE: this could only provide marginal benefits, if the provider is
// a child component of a parent that is re-rendering then this
// may still re-render the context value.
const clientContext = useMemo(() => ({ client }), [client]);

useEffect(() => {
setupListeners(client, setState);
}, []);

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

export default LDProvider;
15 changes: 0 additions & 15 deletions packages/sdk/react-native/src/provider/setupListeners.ts

This file was deleted.

Loading