Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bd29434
Updated License Year
github-actions[bot] Jan 1, 2024
6c85f26
Refactor code to avoid circular dependency
EmilianoSanchez Jan 2, 2024
c75b1ce
Rename test file
EmilianoSanchez Jan 2, 2024
4c32921
Update script
EmilianoSanchez Jan 4, 2024
311fc55
Merge pull request #177 from splitio/update-license
EmilianoSanchez Jan 4, 2024
ab8bbaf
Merge branch 'development' into issue_176_circular_dependency
EmilianoSanchez Jan 4, 2024
65ad71c
Merge pull request #178 from splitio/issue_176_circular_dependency
EmilianoSanchez Jan 4, 2024
329b52b
Add new SplitFactoryProvider component
EmilianoSanchez Jan 10, 2024
1dae653
Update comments
EmilianoSanchez Jan 10, 2024
8bb224b
Update unit tests to use the new SplitFactoryProvider
EmilianoSanchez Jan 10, 2024
0410c0c
Add unit test for the new SplitFactoryProvider
EmilianoSanchez Jan 10, 2024
68dd9fa
Polishing
EmilianoSanchez Jan 11, 2024
cf1f636
Add CHANGES entry
EmilianoSanchez Jan 11, 2024
6bf6b3c
Prepare rc
EmilianoSanchez Jan 11, 2024
1c0b65a
Polishing
EmilianoSanchez Jan 11, 2024
326aa6d
Add new SplitFactoryProvider component and deprecate SplitFactory com…
EmilianoSanchez Jan 11, 2024
c59fe5e
Fix logs for new SplitFactoryProvider component
EmilianoSanchez Jan 11, 2024
528be3e
Merge branch 'fix_logs' into add_SplitFactoryProvider
EmilianoSanchez Jan 11, 2024
068f2a2
Rename ci.yml to ci-cd.yml
EmilianoSanchez Jan 11, 2024
9052984
Merge branch 'fix_logs' into add_SplitFactoryProvider
EmilianoSanchez Jan 11, 2024
53a0a90
Update changelog entry
EmilianoSanchez Jan 11, 2024
0b869f9
Update deprecation comment to add more info
EmilianoSanchez Jan 11, 2024
561d83d
Component prop update test
EmilianoSanchez Jan 15, 2024
1504d59
Optimize SplitFactoryProvider: do not recreate factory if only update…
EmilianoSanchez Jan 16, 2024
045c835
Merge pull request #181 from splitio/add_SplitFactoryProvider
EmilianoSanchez Jan 16, 2024
d50d900
Merge pull request #183 from splitio/fix_logs
EmilianoSanchez Jan 16, 2024
8af925c
Update comments and CHANGES file
EmilianoSanchez Jan 16, 2024
b56d33d
Prepare stable version
EmilianoSanchez Jan 16, 2024
5fa09ae
Merge pull request #182 from splitio/new_SplitFactoryProvider_component
EmilianoSanchez Jan 16, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml → .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up nodejs
uses: actions/setup-node@v3
with:
node-version: '16.16.0'
node-version: 'lts/*'
cache: 'npm'

- name: npm ci
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/update-license-year.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

Expand All @@ -24,7 +24,7 @@ jobs:
run: "echo PREVIOUS=$(($CURRENT-1)) >> $GITHUB_ENV"

- name: Update LICENSE
uses: jacobtomlinson/gha-find-replace@v2
uses: jacobtomlinson/gha-find-replace@v3
with:
find: ${{ env.PREVIOUS }}
replace: ${{ env.CURRENT }}
Expand All @@ -38,7 +38,7 @@ jobs:
git commit -m "Updated License Year" -a

- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: Update License Year
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.16.0
lts/*
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
1.11.0 (January 16, 2023)
- Added the new `SplitFactoryProvider` component as a replacement for the now deprecated `SplitFactory` component.
The new component is a revised version of `SplitFactory`, addressing improper handling of the SDK initialization side-effects in the `componentDidMount` and `componentDidUpdate` methods (commit phase), causing some issues like memory leaks and the SDK not reinitializing when component props change (Related to issue #11 and #148).
The `SplitFactoryProvider` component can be used as a drop-in replacement for `SplitFactory`. It utilizes the React Hooks API, that requires React 16.8.0 or later, and supports server-side rendering. See our documentation for more details (Related to issue #11 and #109).
- Updated internal code to remove a circular dependency and avoid warning messages with tools like PNPM (Related to issue #176).
- Updated @splitsoftware/splitio package to version 10.25.1 for vulnerability fixes.

1.10.2 (December 12, 2023)
- Updated @splitsoftware/splitio package to version 10.24.1 that updates localStorage usage to clear cached feature flag definitions before initiating the synchronization process, if the cache was previously synchronized with a different SDK key (i.e., a different environment) or different Split Filter criteria, to avoid using invalid cached data when the SDK is ready from cache.

Expand All @@ -15,6 +22,7 @@
- `useSplitTreatments` optimizes feature flag evaluations by using the `useMemo` hook to memoize `getTreatmentsWithConfig` method calls from the SDK. This avoids re-evaluating feature flags when the hook is called with the same options and the feature flag definitions have not changed.
- They fixed a bug in the deprecated `useClient` and `useTreatments` hooks, which caused them to not re-render and re-evaluate feature flags when they access a different SDK client than the context and its status updates (i.e., when it emits SDK_READY or other event).
- Added TypeScript types and interfaces to the library index exports, allowing them to be imported from the library index, e.g., `import type { ISplitFactoryProps } from '@splitsoftware/splitio-react'` (Related to issue https://github.com/splitio/react-client/issues/162).
- Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager (Related to issue https://github.com/splitio/javascript-commons/issues/225).
- Updated type declarations of the library components to not restrict the type of the `children` prop to ReactElement, allowing to pass any valid ReactNode value (Related to issue https://github.com/splitio/react-client/issues/164).
- Updated linter and other dependencies for vulnerability fixes.
- Bugfixing - Removed conditional code within hooks to adhere to the rules of hooks and prevent React warnings. Previously, this code checked for the availability of the hooks API (available in React version 16.8.0 or above) and logged an error message. Now, using hooks with React versions below 16.8.0 will throw an error.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright © 2023 Split Software, Inc.
Copyright © 2024 Split Software, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Below is a simple example that describes the instantiation and most basic usage
import React from 'react';

// Import SDK functions
import { SplitFactory, useSplitTreatments } from '@splitsoftware/splitio-react';
import { SplitFactoryProvider, useSplitTreatments } from '@splitsoftware/splitio-react';

// Define your config object
const CONFIG = {
Expand Down Expand Up @@ -48,10 +48,10 @@ function MyComponent() {

function MyApp() {
return (
// Use SplitFactory to instantiate the SDK and makes it available to nested components
<SplitFactory config={CONFIG} >
// Use SplitFactoryProvider to instantiate the SDK and makes it available to nested components
<SplitFactoryProvider config={CONFIG} >
<MyComponent />
</SplitFactory>
</SplitFactoryProvider>
);
}
```
Expand Down
50 changes: 25 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-react",
"version": "1.10.2",
"version": "1.11.0",
"description": "A React library to easily integrate and use Split JS SDK",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down Expand Up @@ -62,7 +62,7 @@
},
"homepage": "https://github.com/splitio/react-client#readme",
"dependencies": {
"@splitsoftware/splitio": "10.24.1",
"@splitsoftware/splitio": "10.25.1",
"memoize-one": "^5.1.1",
"shallowequal": "^1.1.0"
},
Expand Down
13 changes: 3 additions & 10 deletions src/SplitClient.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from 'react';
import { SplitContext } from './SplitContext';
import { ISplitClientProps, ISplitContextValues, IUpdateProps } from './types';
import { ERROR_SC_NO_FACTORY } from './constants';
import { getStatus, getSplitClient, initAttributes, IClientWithContext } from './utils';
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';

/**
* Common component used to handle the status and events of a Split client passed as prop.
* Reused by both SplitFactory (main client) and SplitClient (shared client) components.
* Reused by both SplitFactoryProvider (main client) and SplitClient (any client) components.
*/
export class SplitComponent extends React.Component<IUpdateProps & { factory: SplitIO.IBrowserSDK | null, client: SplitIO.IBrowserClient | null, attributes?: SplitIO.Attributes, children: any }, ISplitContextValues> {

Expand Down Expand Up @@ -47,11 +46,6 @@ export class SplitComponent extends React.Component<IUpdateProps & { factory: Sp
super(props);
const { factory, client } = props;

// Log error if factory is not available
if (!factory) {
console.error(ERROR_SC_NO_FACTORY);
}

this.state = {
factory,
client,
Expand Down Expand Up @@ -129,9 +123,8 @@ export class SplitComponent extends React.Component<IUpdateProps & { factory: Sp
* SplitClient will initialize a new SDK client and listen for its events in order to update the Split Context.
* Children components will have access to the new client when accessing Split Context.
*
* Unlike SplitFactory, the underlying SDK client can be changed during the component lifecycle
* if the component is updated with a different splitKey or trafficType prop. Since the client can change,
* its release is not handled by SplitClient but by its container SplitFactory component.
* The underlying SDK client can be changed during the component lifecycle
* if the component is updated with a different splitKey or trafficType prop.
*
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#advanced-instantiate-multiple-sdk-clients}
*/
Expand Down
2 changes: 1 addition & 1 deletion src/SplitContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export const INITIAL_CONTEXT: ISplitContextValues = {
/**
* Split Context is the React Context instance that represents our SplitIO global state.
* It contains Split SDK objects, such as a factory instance, a client and its status (isReady, isTimedout, lastUpdate)
* The context is created with default empty values, that eventually SplitFactory and SplitClient access and update.
* The context is created with default empty values, that SplitFactoryProvider and SplitClient access and update.
*/
export const SplitContext = React.createContext<ISplitContextValues>(INITIAL_CONTEXT);
9 changes: 7 additions & 2 deletions src/SplitFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';
/**
* SplitFactory will initialize the Split SDK and its main client, listen for its events in order to update the Split Context,
* and automatically shutdown and release resources when it is unmounted. SplitFactory must wrap other components and functions
* from this library, since they access the Split Context and its elements (factory, clients, etc).
* from this library, since they access the Split Context and its properties (factory, client, isReady, etc).
*
* The underlying SDK factory and client is set on the constructor, and cannot be changed during the component lifecycle,
* even if the component is updated with a different config or factory prop.
*
* @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK}
* @deprecated Replace with the new `SplitFactoryProvider` component.
* `SplitFactoryProvider` is a drop-in replacement that properly handles side effects (factory creation and destruction) within the React component lifecycle, avoiding issues with factory recreation and memory leaks.
* Note: There is a subtle breaking change in `SplitFactoryProvider`. When using the `config` prop, `factory` and `client` properties in the context are `null` in the first render, until the context is updated when some event is emitted on
* the SDK main client (ready, ready from cache, timeout or update depending on the configuration of the `updateOnXXX` props of the component). This differs from the previous behavior where `factory` and `client` were immediately available.
*
* @see {@link https://help.split.io/hc/en-us/articles/360038825091-React-SDK#2-instantiate-the-sdk-and-create-a-new-split-client}
*/
export class SplitFactory extends React.Component<ISplitFactoryProps, { factory: SplitIO.IBrowserSDK | null, client: SplitIO.IBrowserClient | null }> {

Expand Down
86 changes: 86 additions & 0 deletions src/SplitFactoryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react';

import { SplitComponent } from './SplitClient';
import { ISplitFactoryProps } from './types';
import { WARN_SF_CONFIG_AND_FACTORY } from './constants';
import { getSplitFactory, destroySplitFactory, IFactoryWithClients, getSplitClient, getStatus, __factories } from './utils';
import { DEFAULT_UPDATE_OPTIONS } from './useSplitClient';

/**
* SplitFactoryProvider will initialize the Split SDK and its main client when `config` prop is provided or updated, listen for its events in order to update the Split Context,
* and automatically destroy the SDK (shutdown and release resources) when it is unmounted or `config` prop updated. SplitFactoryProvider must wrap other library components and
* functions since they access the Split Context and its properties (factory, client, isReady, etc).
*
* NOTE: Either pass a factory instance or a config object. If both are passed, the config object will be ignored.
* Pass the same reference to the config or factory object rather than a new instance on each render, to avoid unnecessary props changes and SDK reinitializations.
*
* @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: ISplitFactoryProps) {
let {
config, factory: propFactory,
updateOnSdkReady, updateOnSdkReadyFromCache, updateOnSdkTimedout, updateOnSdkUpdate
} = { ...DEFAULT_UPDATE_OPTIONS, ...props };

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

const [configFactory, setConfigFactory] = React.useState<IFactoryWithClients | null>(null);
const factory = propFactory || (configFactory && config === configFactory.config ? configFactory : null);
const client = factory ? getSplitClient(factory) : null;

// Effect to initialize and destroy the factory
React.useEffect(() => {
if (config) {
const factory = getSplitFactory(config);

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

// Effect to subscribe/unsubscribe to events
React.useEffect(() => {
const factory = config && __factories.get(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]);

return (
<SplitComponent {...props} factory={factory} client={client} />
);
}
Loading