react-client
react-client copied to clipboard
Users are getting null is not an object (evaluating 'n.state.client.lastUpdate')
Hey, I don't know if this is a widespread issue or just an issue on my end. I'm seeing a big amount of errors in Sentry of this issue:
TypeError _this.update(@[email protected][email protected]/node_modules/@splitsoftware/splitio-react/es/SplitClient) Unhandled: null is not an object (evaluating 'n.state.client.lastUpdate')
After searching the web and finding nothing about this issue, I dove into the code and found where I think the error occurs:
// Attach listeners for SDK events, to update state if client status change.
// The listeners take into account the value of `updateOnSdk***` props.
subscribeToEvents(client: SplitIO.IBrowserClient | null) {
if (client) {
const statusOnEffect = getStatus(client);
const status = this.state;
if (this.props.updateOnSdkReady) {
if (!statusOnEffect.isReady) client.once(client.Event.SDK_READY, this.update);
else if (!status.isReady) this.update();
}
if (this.props.updateOnSdkReadyFromCache) {
if (!statusOnEffect.isReadyFromCache) client.once(client.Event.SDK_READY_FROM_CACHE, this.update);
else if (!status.isReadyFromCache) this.update();
}
if (this.props.updateOnSdkTimedout) {
if (!statusOnEffect.hasTimedout) client.once(client.Event.SDK_READY_TIMED_OUT, this.update);
else if (!status.hasTimedout) this.update();
}
if (this.props.updateOnSdkUpdate) client.on(client.Event.SDK_UPDATE, this.update);
}
}
This is my setup of the SplitClient:
import { useEffect, useState } from "react";
import {
SplitClient,
SplitFactoryProvider,
} from "@splitsoftware/splitio-react";
import { analytics } from "@flare/analytics";
const DUMMY_SPLIT_KEY = "anonymous";
export const SplitProvider = ({ children }: { children: React.ReactNode }) => {
const [anonymousId, setAnonymousId] = useState<string | null | undefined>();
useEffect(() => {
async function getAnonymousId() {
const aId = await analytics.getAnonymousId();
setAnonymousId(aId);
}
getAnonymousId();
}, []);
const splitConfig: SplitIO.IBrowserSettings = {
core: {
authorizationKey: process.env.NEXT_PUBLIC_SPLIT_CLIENT_KEY as string,
key: DUMMY_SPLIT_KEY,
trafficType: "anonymous",
},
};
if (anonymousId) {
splitConfig.core.key = anonymousId;
}
return (
<SplitFactoryProvider config={splitConfig}>
<SplitClient splitKey={splitConfig.core.key}>{children}</SplitClient>
</SplitFactoryProvider>
);
};
When I log the isReady and client props i see that client is null when isReady is still false.
Does this mean i have to lazy load the SplitClient and my entire component tree that passes as children (render only when isReady === true)?
Or am I missing something in my setup?
Hi @neriyarden,
Yes, that behavior (client is null when isReady is still false) is expected for the SplitFactoryProvider component. This is a breaking change compared to the now deprecated SplitFactory component.
In summary, the client property is null during the first render to avoid side-effects and follow the "rules" of React components. You can read more about it in the following references:
- https://github.com/splitio/react-client/blob/master/MIGRATION-GUIDE.md#migrating-to-get-react-sdk-v1110-improvements-replacing-the-deprecated-splitfactory-and-withsplitfactory-components
- https://github.com/splitio/react-client/issues/186#issuecomment-1906053110
You don't necessarily have to lazy load the SplitClient or your component, but you should conditionally render based on the value of the isReady property (or isReadyFromCache). For example:
import { useSplitClient } from "@splitsoftware/splitio-react";
const MySplitClientChildrenComponent = (props) => {
const { isReady, isReadyFromCache, client } = useSplitClient();
return isReady || isReadyFromCache ?
<p>`My treatment is ${client.getTreatment(MY_FEATURE_FLAG}`</p> : // client is available
<Loading /> // client is null
}
But you can directly avoid using the client at all, for example:
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const MySplitClientChildrenComponent = (props) => {
const { isReady, isReadyFromCache, treatments } = useSplitTreatments({ names: [MY_FEATURE_FLAG] });
return isReady || isReadyFromCache ?
<p>`My treatment is ${treatments[MY_FEATURE_FLAG].treatment}`</p> :
<Loading />
}
Hope this helps!
Hi @neriyarden,
We have released v2 of the React SDK. In this version, the SplitFactoryProvider component makes the factory and client objects available from the initial render, so you should no longer face the same error.
This behavior was reverted to address an issue when tracking events using the useTrack or useSplitClient hooks, which require the client instance to be available.
Please keep in mind:
- When possible, avoid using the
clientinstance directly, as there are more "React-ish" alternatives:useSplitTreatmentsfor evaluating feature flags anduseTrackfor tracking events. - The
factoryinstance should only be used for features that do not have a "React-ish" alternative, such as Logging and User Consent configuration. - The code snippets I shared here still apply.
I’ll be closing this issue for now. Feel free to re-open it if you encounter a similar problem.
Thank you!