Skip to content

Commit

Permalink
Merge pull request #184 from splitio/development
Browse files Browse the repository at this point in the history
Release v1.11.0
  • Loading branch information
EmilianoSanchez authored Jan 16, 2024
2 parents 06df6d7 + 5fa09ae commit d908180
Show file tree
Hide file tree
Showing 34 changed files with 765 additions and 214 deletions.
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

0 comments on commit d908180

Please sign in to comment.