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

[Breaking change] factory, client, and track method available in initial render when using config prop. #211

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
628b185
Make factory and client context properties available in first render …
EmilianoSanchez Oct 23, 2024
833d6c9
Added test to validate bugfix
EmilianoSanchez Oct 24, 2024
6bfb15b
Polishing
EmilianoSanchez Oct 24, 2024
e1bad0a
Added test to validate useTrack does not re-render
EmilianoSanchez Oct 24, 2024
e262600
Polishing
EmilianoSanchez Oct 24, 2024
fec84b3
Polishing
EmilianoSanchez Oct 24, 2024
5009c20
Merge pull request #203 from splitio/remove_deprecated_modules
EmilianoSanchez Oct 24, 2024
e633ad2
Merge pull request #204 from splitio/rename_SplitSdk_to_SplitFactory
EmilianoSanchez Oct 24, 2024
7691dd9
Merge pull request #205 from splitio/react_v2_functional_components
EmilianoSanchez Oct 24, 2024
c33577f
Merge pull request #206 from splitio/error_handling_updates
EmilianoSanchez Oct 24, 2024
2cbc1d0
Merge branch 'breaking_changes_baseline' into issue_198_factory_avail…
EmilianoSanchez Oct 24, 2024
bafcfd9
Update JS SDK to use updated Type declaration files
EmilianoSanchez Oct 25, 2024
465af41
Fix import
EmilianoSanchez Oct 25, 2024
2d1a0d0
test namespace implicit import
EmilianoSanchez Oct 26, 2024
dff8b1f
Update changelog entry
EmilianoSanchez Oct 26, 2024
2c311b9
Add TSDoc linter rules
EmilianoSanchez Oct 29, 2024
d26572e
Update changelog entry
EmilianoSanchez Oct 30, 2024
eb475cd
Upgrade JS SDK
EmilianoSanchez Oct 31, 2024
54b13ca
Polishing
EmilianoSanchez Oct 31, 2024
41d960b
rollback ci-cd
EmilianoSanchez Oct 31, 2024
6a723c7
Merge pull request #213 from splitio/refactor_type_definitions
EmilianoSanchez Oct 31, 2024
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 CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- Added support for targeting rules based on large segments for browsers.
- Updated @splitsoftware/splitio package to version 11.0.0 that includes major updates, and updated some transitive dependencies for vulnerability fixes.
- Renamed distribution folders from `/lib` to `/cjs` for CommonJS build, and `/es` to `/esm` for EcmaScript Modules build.
- Bugfixing - When the `config` prop is provided, the `SplitFactoryProvider` now makes the SDK factory and client instances available in the context immediately during the initial render, instead of waiting for the first SDK event (Related to https://github.com/splitio/react-client/issues/198). This change fixes a bug in the `useTrack` hook, which was not retrieving the client's `track` method during the initial render.
- BREAKING CHANGES:
- Updated error handling: using the library modules without wrapping them in a `SplitFactoryProvider` component will now throw an error instead of logging it, as the modules requires the `SplitContext` to work properly.
- Removed the `core.trafficType` configuration option and the `trafficType` parameter from the SDK `client()` method, `useSplitClient`, `useTrack`, and `SplitClient` component. This is because traffic types can no longer be bound to SDK clients in JavaScript SDK v11.0.0, and so the traffic type must be provided as first argument in the `track` method calls.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"check": "npm run check:lint && npm run check:types",
"check:lint": "eslint 'src/**/*.ts*'",
"check:types": "tsc --noEmit",
"test": "jest src",
"test": "jest src --silent",
"test:watch": "npm test -- --watch",
"test:coverage": "jest src --coverage",
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",
Expand Down
64 changes: 11 additions & 53 deletions src/SplitFactoryProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import React from 'react';
import { SplitClient } from './SplitClient';
import { ISplitFactoryProviderProps } from './types';
import { WARN_SF_CONFIG_AND_FACTORY } from './constants';
import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus } from './utils';
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';
import { getSplitFactory, destroySplitFactory, getSplitClient, getStatus } from './utils';
import { SplitContext } from './SplitContext';

/**
Expand All @@ -18,70 +17,29 @@ import { SplitContext } from './SplitContext';
* @see {@link https://help.split.io/hc/en-us/articles/360038825091-React-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
*/
export function SplitFactoryProvider(props: ISplitFactoryProviderProps) {
let {
config, factory: propFactory,
updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate
} = { ...DEFAULT_UPDATE_OPTIONS, ...props };
const { config, factory: propFactory } = props;

if (config && propFactory) {
console.log(WARN_SF_CONFIG_AND_FACTORY);
config = undefined;
}

const [configFactory, setConfigFactory] = React.useState<IFactoryWithClients>();
const factory = React.useMemo(() => {
return propFactory || (configFactory && config === configFactory.config ? configFactory : undefined);
}, [config, propFactory, configFactory]);
return propFactory || (config ? getSplitFactory(config) : undefined);
}, [config, propFactory]);
const client = factory ? getSplitClient(factory) : undefined;

// Effect to initialize and destroy the factory
React.useEffect(() => {
if (propFactory) {
if (config) console.log(WARN_SF_CONFIG_AND_FACTORY);
return;
}

if (config) {
const factory = getSplitFactory(config);
factory.init && factory.init();

return () => {
destroySplitFactory(factory);
}
}
}, [config]);

// Effect to subscribe/unsubscribe to events
React.useEffect(() => {
const factory = config && getSplitFactory(config);
if (factory) {
const client = getSplitClient(factory);
const status = getStatus(client);

// Unsubscribe from events and update state when first event is emitted
const update = () => { // eslint-disable-next-line no-use-before-define
unsubscribe();
setConfigFactory(factory);
}

const unsubscribe = () => {
client.off(client.Event.SDK_READY, update);
client.off(client.Event.SDK_READY_FROM_CACHE, update);
client.off(client.Event.SDK_READY_TIMED_OUT, update);
client.off(client.Event.SDK_UPDATE, update);
}

if (updateOnSdkReady) {
if (status.isReady) update();
else client.once(client.Event.SDK_READY, update);
}
if (updateOnSdkReadyFromCache) {
if (status.isReadyFromCache) update();
else client.once(client.Event.SDK_READY_FROM_CACHE, update);
}
if (updateOnSdkTimedout) {
if (status.hasTimedout) update();
else client.once(client.Event.SDK_READY_TIMED_OUT, update);
}
if (updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, update);

return unsubscribe;
}
}, [config, updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate]);
}, [config, propFactory]);

return (
<SplitContext.Provider value={{
Expand Down
2 changes: 1 addition & 1 deletion src/SplitTreatments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useSplitTreatments } from './useSplitTreatments';
export function SplitTreatments(props: ISplitTreatmentsProps) {
const { children } = props;
// SplitTreatments doesn't update on SDK events, since it is inside SplitFactory and/or SplitClient.
const context = useSplitTreatments({ updateOnSdkReady: false, updateOnSdkTimedout: false, ...props });
const context = useSplitTreatments({ ...props, updateOnSdkReady: false, updateOnSdkReadyFromCache: false });

return (
<SplitContext.Provider value={context}>
Expand Down
17 changes: 10 additions & 7 deletions src/__tests__/SplitClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render, act } from '@testing-library/react';

/** Mocks and test utils */
import { mockSdk, Event } from './testUtils/mockSplitFactory';
import { mockSdk, Event, getLastInstance } from './testUtils/mockSplitFactory';
jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
Expand All @@ -14,7 +14,7 @@ import { ISplitClientChildProps } from '../types';
import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { INITIAL_CONTEXT, testAttributesBinding, TestComponentProps } from './testUtils/utils';
import { INITIAL_STATUS, testAttributesBinding, TestComponentProps } from './testUtils/utils';
import { IClientWithContext } from '../utils';
import { EXCEPTION_NO_SFP } from '../constants';

Expand All @@ -25,7 +25,11 @@ describe('SplitClient', () => {
<SplitFactoryProvider config={sdkBrowser} >
<SplitClient splitKey='user1' >
{(childProps: ISplitClientChildProps) => {
expect(childProps).toEqual(INITIAL_CONTEXT);
expect(childProps).toEqual({
...INITIAL_STATUS,
factory: getLastInstance(SplitFactory),
client: getLastInstance(SplitFactory).client('user1'),
});

return null;
}}
Expand All @@ -47,7 +51,7 @@ describe('SplitClient', () => {
<SplitClient splitKey={sdkBrowser.core.key} >
{(childProps: ISplitClientChildProps) => {
expect(childProps).toEqual({
...INITIAL_CONTEXT,
...INITIAL_STATUS,
factory : outerFactory,
client: outerFactory.client(),
isReady: true,
Expand Down Expand Up @@ -210,8 +214,7 @@ describe('SplitClient', () => {
count++;

// side effect in the render phase
if (!(client as IClientWithContext).__getStatus().isReady) {
console.log('emit');
if (!(client as any).__getStatus().isReady) {
(client as any).__emitter__.emit(Event.SDK_READY);
}

Expand All @@ -232,7 +235,7 @@ describe('SplitClient', () => {
<SplitContext.Consumer>
{(value) => {
expect(value).toEqual({
...INITIAL_CONTEXT,
...INITIAL_STATUS,
factory: outerFactory,
client: outerFactory.client('user2'),
});
Expand Down
8 changes: 6 additions & 2 deletions src/__tests__/SplitContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render } from '@testing-library/react';
import { SplitContext } from '../SplitContext';
import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { INITIAL_CONTEXT } from './testUtils/utils';
import { INITIAL_STATUS } from './testUtils/utils';

/**
* Test default SplitContext value
Expand All @@ -23,7 +23,11 @@ test('SplitContext.Consumer shows value when wrapped in a SplitFactoryProvider',
<SplitFactoryProvider >
<SplitContext.Consumer>
{(value) => {
expect(value).toEqual(INITIAL_CONTEXT);
expect(value).toEqual({
...INITIAL_STATUS,
factory: undefined,
client: undefined
});
return null;
}}
</SplitContext.Consumer>
Expand Down
36 changes: 22 additions & 14 deletions src/__tests__/SplitFactoryProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { render, act } from '@testing-library/react';

/** Mocks */
import { mockSdk, Event } from './testUtils/mockSplitFactory';
import { mockSdk, Event, getLastInstance } from './testUtils/mockSplitFactory';
jest.mock('@splitsoftware/splitio/client', () => {
return { SplitFactory: mockSdk() };
});
Expand All @@ -17,15 +17,19 @@ import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { __factories, IClientWithContext } from '../utils';
import { WARN_SF_CONFIG_AND_FACTORY } from '../constants';
import { INITIAL_CONTEXT } from './testUtils/utils';
import { INITIAL_STATUS } from './testUtils/utils';

describe('SplitFactoryProvider', () => {

test('passes no-ready props to the child if initialized with a config.', () => {
render(
<SplitFactoryProvider config={sdkBrowser} >
{(childProps: ISplitFactoryProviderChildProps) => {
expect(childProps).toEqual(INITIAL_CONTEXT);
expect(childProps).toEqual({
...INITIAL_STATUS,
factory: getLastInstance(SplitFactory),
client: getLastInstance(SplitFactory).client(),
});
return null;
}}
</SplitFactoryProvider>
Expand All @@ -43,7 +47,7 @@ describe('SplitFactoryProvider', () => {
<SplitFactoryProvider factory={outerFactory} >
{(childProps: ISplitFactoryProviderChildProps) => {
expect(childProps).toEqual({
...INITIAL_CONTEXT,
...INITIAL_STATUS,
factory: outerFactory,
client: outerFactory.client(),
isReady: true,
Expand Down Expand Up @@ -84,7 +88,7 @@ describe('SplitFactoryProvider', () => {
default:
fail('Child must not be rerendered');
} // eslint-disable-next-line no-use-before-define
if (factory) expect(factory).toBe(innerFactory);
expect(factory).toBe(innerFactory || getLastInstance(SplitFactory));
expect(lastUpdate).toBeGreaterThan(previousLastUpdate);
renderTimes++;
previousLastUpdate = lastUpdate;
Expand All @@ -93,7 +97,7 @@ describe('SplitFactoryProvider', () => {
</SplitFactoryProvider>
);

const innerFactory = (SplitFactory as jest.Mock).mock.results.slice(-1)[0].value;
const innerFactory = getLastInstance(SplitFactory);
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY_FROM_CACHE));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY));
Expand Down Expand Up @@ -168,7 +172,7 @@ describe('SplitFactoryProvider', () => {
default:
fail('Child must not be rerendered');
} // eslint-disable-next-line no-use-before-define
if (factory) expect(factory).toBe(innerFactory);
expect(factory).toBe(innerFactory || getLastInstance(SplitFactory));
expect(lastUpdate).toBeGreaterThan(previousLastUpdate);
renderTimes++;
previousLastUpdate = lastUpdate;
Expand All @@ -177,7 +181,7 @@ describe('SplitFactoryProvider', () => {
</SplitFactoryProvider>
);

const innerFactory = (SplitFactory as jest.Mock).mock.results.slice(-1)[0].value;
const innerFactory = getLastInstance(SplitFactory);
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_UPDATE));
Expand Down Expand Up @@ -241,7 +245,7 @@ describe('SplitFactoryProvider', () => {
default:
fail('Child must not be rerendered');
} // eslint-disable-next-line no-use-before-define
if (factory) expect(factory).toBe(innerFactory);
expect(factory).toBe(innerFactory || getLastInstance(SplitFactory));
expect(lastUpdate).toBeGreaterThan(previousLastUpdate);
renderTimes++;
previousLastUpdate = lastUpdate;
Expand All @@ -250,7 +254,7 @@ describe('SplitFactoryProvider', () => {
</SplitFactoryProvider>
);

const innerFactory = (SplitFactory as jest.Mock).mock.results.slice(-1)[0].value;
const innerFactory = getLastInstance(SplitFactory);
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_READY));
act(() => (innerFactory as any).client().__emitter__.emit(Event.SDK_UPDATE));
Expand Down Expand Up @@ -296,7 +300,11 @@ describe('SplitFactoryProvider', () => {
return (
<SplitContext.Consumer>
{(value) => {
expect(value).toEqual(INITIAL_CONTEXT);
expect(value).toEqual({
...INITIAL_STATUS,
factory: getLastInstance(SplitFactory),
client: getLastInstance(SplitFactory).client(),
});
done();
return null;
}}
Expand Down Expand Up @@ -344,14 +352,14 @@ describe('SplitFactoryProvider', () => {
case 5:
expect(isReady).toBe(false);
expect(hasTimedout).toBe(false);
expect(factory).toBe(undefined);
expect(factory).toBe(getLastInstance(SplitFactory));
return null;
case 3:
case 4:
case 6:
expect(isReady).toBe(true);
expect(hasTimedout).toBe(true);
expect(factory).not.toBe(undefined);
expect(factory).toBe(getLastInstance(SplitFactory));
createdFactories.add(factory!);
clientDestroySpies.push(jest.spyOn(factory!.client(), 'destroy'));
return (
Expand All @@ -368,7 +376,7 @@ describe('SplitFactoryProvider', () => {
};

const emitSdkEvents = () => {
const factory = (SplitFactory as jest.Mock).mock.results.slice(-1)[0].value;
const factory = getLastInstance(SplitFactory);
factory.client().__emitter__.emit(Event.SDK_READY_TIMED_OUT)
factory.client().__emitter__.emit(Event.SDK_READY)
};
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/testUtils/mockSplitFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,7 @@ export function mockSdk() {
});

}

export function getLastInstance(SplitFactoryMock: any) {
return SplitFactoryMock.mock.results.slice(-1)[0].value;
}
6 changes: 2 additions & 4 deletions src/__tests__/testUtils/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
import { ISplitContextValues } from '../../types';
import { ISplitStatus } from '../../types';
const { SplitFactory: originalSplitFactory } = jest.requireActual('@splitsoftware/splitio/client');

export interface TestComponentProps {
Expand Down Expand Up @@ -116,9 +116,7 @@ export function testAttributesBinding(Component: React.FunctionComponent<TestCom
wrapper.rerender(<Component splitKey={undefined} attributesFactory={undefined} attributesClient={undefined} testSwitch={attributesBindingSwitch} factory={factory} />);
}

export const INITIAL_CONTEXT: ISplitContextValues = {
client: undefined,
factory: undefined,
export const INITIAL_STATUS: ISplitStatus = {
isReady: false,
isReadyFromCache: false,
isTimedout: false,
Expand Down
8 changes: 2 additions & 6 deletions src/__tests__/useSplitManager.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getStatus } from '../utils';
import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { useSplitManager } from '../useSplitManager';
import { EXCEPTION_NO_SFP } from '../constants';
import { INITIAL_STATUS } from './testUtils/utils';

describe('useSplitManager', () => {

Expand All @@ -33,12 +34,7 @@ describe('useSplitManager', () => {
manager: outerFactory.manager(),
client: outerFactory.client(),
factory: outerFactory,
hasTimedout: false,
isDestroyed: false,
isReady: false,
isReadyFromCache: false,
isTimedout: false,
lastUpdate: 0,
...INITIAL_STATUS,
});

act(() => (outerFactory.client() as any).__emitter__.emit(Event.SDK_READY));
Expand Down
Loading
Loading