Closed oferitz closed 9 months ago
Hi @oferitz
Thanks for sharing. I will first explain the reason for your issue, then make a refactor suggestion to fix it, and finally a few more code refactor suggestions.
There is a breaking change behaviour in SplitFactoryProvider
with respect to SplitFactory
that is causing your code to hang. I explained this change here, but we haven't included it in our docs. So I am sorry for that.
Basically, the SplitFactoryProvider
creates the factory (and so, its client) in the "componentDidMount" method, while SplitFactory
does it in the "constructor". This implies that in the 1st render of your components, the client
and factory
properties are null if using SplitFactoryProvider
, while they are defined if using SplitFactory
. In the second render, client
property is defined but its SDK_READY event has already been emitted, and so your client.Event.SDK_READY
callback is not invoked.
const FeatureFlagsProvider = ({ children }: React.PropsWithChildren) => {
const { client, factory, isReady } = useSplitClient()
// Inside a SplitFactoryProvider component, next line prints:
// `null null false` in the 1st render
// `[Object] [Object] true` in the 2nd render when SDK_READY event is emitted
// Inside a SplitFactory component, next line prints:
// `[Object] [Object] false` in the 1st render
// `[Object] [Object] true` in the 2nd render when SDK_READY event is emitted
console.log(client, factory, isReady);
...
}
You can refactor your second code snippet as follows: You don't need to listen for the client events, because the client readiness state is already accessible through the isReady
and isReadyFromCache
properties of the context:
import React from 'react'
import { useSplitClient } from '@splitsoftware/splitio-react'
const FeatureFlagsProvider = ({ children }: React.PropsWithChildren) => {
const { client, isReady, isReadyFromCache } = useSplitClient();
const splitReady = isReady || isReadyFromCache;
return splitReady ? <>{children}</> : <FullPageSpinner />
}
export { FeatureFlagsProvider }
Some extra suggestions:
FeatureFlagsProvider
component will re-render by default when SDK_READY (isReady === true
) and SDK_READY_FROM_CACHE (isReadyFromCache === true
) events are emitted, but not when SDK_UPDATE event is emitted. If you want your component to also update on SDK_UPDATE events, you have to set the updateOnSdkUpdate
property to true
either in your SplitFactoryProvider
or when calling the useSplitClient
hook:<SplitFactoryProvider config={myConfig} updateOnSdkUpdate={true} ><MyComponent /></SplitFactoryProvider>
const { client, ... } = useSplitClient({ updateOnSdkUpdate: true });
client
property directly. Instead, use the useSplitTreatments
and useTrack
hooks for the client.getTreatment*
and client.track
methods respectively. useSplitTreatments
calls useSplitClient
under the hood, and also exposes the readiness state of the client:const { treatments, isReady, isReadyFromCache } = useSplitTreatments({ names: ['feature_1'], updateOnSdkUpdate: true /* false by default */ });
user.id
just changes once in your SplitProvider
component (e.g., initially undefined
and then 'user_1'
). But if it changes frequently, consider the following refactor to avoid destroying and creating a new Split factory each time the user.id
changes:import * as React from 'react'
import { SplitFactoryProvider, SplitClient } from '@splitsoftware/splitio-react'
import { FullPageSpinner } from 'components/Loader'
import { useUser } from 'context/UserProvider'
const splitConfig = {
core: {
authorizationKey: SPLIT_API_KEY,
key: 'anonymous' // to use when `user.id` is not defined
},
impressionListener: {
logImpression(impressionData) {
datadogRum.addFeatureFlagEvaluation(impressionData.impression.feature, impressionData.impression.treatment)
}
}
};
const SplitProvider = ({ children }: React.PropsWithChildren) => {
const user = useUser()
return (
<SplitFactoryProvider config={splitConfig}>{
user.id ?
(<SplitClient splitKey={user.id} >{children}</SplitClient>) :
(<FullPageSpinner />)
}</SplitFactoryProvider>
);
}
export default SplitProvider
@EmilianoSanchez, thank you for the thorough explanation and additional suggestions! It really makes sense now. I will try to implement it according to your answer.
Closing. The suggested solution is working.
Upgrading to version 1.11.0 with
SplitFactoryProvider
causes the application to hang indefinitely. However, the same version (1.11.0) works with the deprecatedSplitFactory
. My custom provider code is as follows:The child component listens to Split client events, and with the new provider,
client.Event.SDK_READY
is never called, nor isclient.Event.SDK_READY_TIMED_OUT
. Since these events are used to determine the loading state, the application hangs. The child component code is as follows:Switching from
SplitFactory
toSplitFactoryProvider
causes the application to hang indefinitely becausesplitReady
is never set totrue
. No console errors or visible failures are apparent in the network.