-
Notifications
You must be signed in to change notification settings - Fork 36
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
Automatic sticky bucketing for logged out users if ID is unset #145
Comments
The suggested option with IP / user agent does not sound like valid / achievable option, because during SSR it is not possible to determine such values from the
You can create a cookie with a random unique value, that will stay in the customer browser for years (i.e.
This sound like an issue with the way you integrated your application with Optimizely |
This wraps my main app components import { FC } from "react";
import { OptimizelyProvider, ReactSDKClient } from "@optimizely/react-sdk";
import { getOptimizelyInstance } from "src/services/optimizely";
import { useFundraiserContext } from "src/contexts/FundraiserContext";
import { isBrowser } from "src/utils/browserUtils";
import { useLocalStorage } from "src/hooks/useLocalStorage";
import useLayoutEffectBrowser from "src/hooks/useLayoutEffectBrowser";
// not the most secure but this ID generator is only for optimizely
const uuidv4 = (): string => {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
export const OptimizelyWrapper: FC<{}> = ({ children }) => {
// this is just createInstance() with SDK key pumped in
const client: ReactSDKClient = getOptimizelyInstance();
const [fundraiserState] = useFundraiserContext();
const { fundraiser } = fundraiserState;
// first prop is the key name and the second is the default before window is ready
// in the experiments I filter out anyone with the user id of "logged_out"
const [optimizelyId, setOptimizelyId] = useLocalStorage(
"optimizely",
"logged_out"
);
// only runs when there is window not run SSR
useLayoutEffectBrowser(() => {
if (optimizelyId === "logged_out" && !fundraiser?.uniqueId)
setOptimizelyId(uuidv4());
}, []);
// use logged in user id else logged out user id
const userID = fundraiser?.uniqueId || optimizelyId;
// I wish I didn't have to check for optimizely client, +1 for the PR by fabb that fixes this
// probably causes a re-render
return (
<>
{client ? (
<OptimizelyProvider
optimizely={client}
user={{
id: userID,
attributes: {
user_id: userID,
is_logged_in: !!fundraiser?.uniqueId,
},
}}
isServerSide={!isBrowser}
>
{children}
</OptimizelyProvider>
) : (
<>{children}</>
)}
</>
);
};
export default OptimizelyWrapper; |
@Develliot since you are using SSR ( It has the issue I described here: #87, but there is a chance it got fixed in recent updates of the SDK. I also described there potential workaround, but I am not sure if this still is a thing with the latest version of What it does:
With that in place there should be no re-renders. Re-renders should happen only when the 1. As for user ID, it is up to you how you want to handle that. You can use two different sources:
|
Thanks @oMatej I will have look at implementing cookies. How important is datafile? When using things like useDecision its user/bucket specific, the the datafile from server looks like more general info for feature switching, could it be be blank and would useDecision still work? With regards to user ID I'm pretty much generating it as you describe. The trick it going to get that stuff accessible server side. |
The Providing |
I if the datafile is crucial why does the async loading example on the main readme doesn't not even bother with it. Unfortunately the user ID from cookies back into the response request isn't going to work in my use case because that is going to screw up my caching strategy. All pages have a logged in and logged out view, logged in data happens async and for most people viewing the pages they will see the highly cached logged out view. If I start passing cookies down with the request it is going to break caching. I just want to be able to update the user id async and only re-render if the use decision says it should post hydration. Other wise i don't want it to re-render |
Why do I even have to instantiate a client why can't I just pass my SDK key into the provider and let the provider handle everything? |
Of course it does. It just fetch the
During SSR your goal should be to render exactly the same content on the server, and in the browser when the hydration process is happening. That is why You can initialize Optimizely only on the client side. However, it might cause errors with It is the same thing as doing SSR with i.e. |
I think my first problem is that the Provider component is completely useless in my use case because it is so rigid I wish the provider looked more like this, so I can get it and set it with a hook from anywhere in my app with import React, {
FC,
useState,
createContext,
useContext,
Dispatch,
SetStateAction,
} from "react";
import { ReactSDKClient } from "@optimizely/react-sdk";
export type OptimizelyContextStateType = {
optimizely: ReactSDKClient | null;
isServerSide: boolean;
timeout: number | undefined;
};
const defaultState: OptimizelyContextStateType = {
optimizely: null,
isServerSide: false,
timeout: 0,
};
export type OptimizelyContextProviderType = [
OptimizelyContextStateType,
Dispatch<SetStateAction<OptimizelyContextStateType>>
];
export const OptimizelyContext = createContext<OptimizelyContextProviderType>([
{ ...defaultState },
() => {},
]);
export const useOptimizelyContext = () => useContext(OptimizelyContext);
export const OptimizelyContextProvider: FC = ({ children }) => {
const [state, setState] = useState({
...defaultState,
});
return (
<OptimizelyContext.Provider value={[state, setState]}>
{children}
</OptimizelyContext.Provider>
);
}; No user ID stuff at all, and see the setter is exposed Then you can choose how you set the user for you could do directly in the component so it renders SSR or I could do something like this to it works client side. export const OptimizelyLoader: FC<{}> = ({ children }) => {
const { trackError } = useTrackingContext();
const [fundraiserState] = useFundraiserContext();
const { fundraiser } = fundraiserState;
const [optimizelyData, setOptimizelyData] = useOptimizelyContext();
const { optimizely } = optimizelyData;
// "logged_out" is default effectively user_id unset
const [optimizelyId, setOptimizelyId] = useLocalStorage(
"optimizely",
"logged_out"
);
const uuidv4 = (): string => {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
};
const getClientInstance = async () => {
try {
const resp = await fetch(
`https://cdn.optimizely.com/datafiles/${OPTIMIZELY_SDK_KEY}.json`
);
const datafile = await resp.json();
const instance = createInstance({
datafile: datafile,
});
return instance;
} catch (err) {
trackError(err as Error);
return null;
}
};
useLayoutEffectBrowser(() => {
if (!OPTIMIZELY_SDK_KEY.length) return;
if (optimizelyId === "logged_out" && !fundraiser?.uniqueId) {
setOptimizelyId(uuidv4());
}
getClientInstance().then((client: ReactSDKClient | null) => {
if (!!client) {
setOptimizelyData({
...optimizelyData,
optimizely: client,
});
}
});
}, []);
useEffect(() => {
const userID = fundraiser?.uniqueId || optimizelyId;
if (userID && optimizely) {
const userData = {
id: userID,
attributes: {
user_id: userID,
is_logged_in: !!fundraiser?.uniqueId,
},
};
optimizely.setUser(userData);
}
}, [optimizely, optimizelyId, fundraiser]);
return <></>;
};
export default OptimizelyLoader; If the useDecision hook checked for optimizely client on the provider and returned an error object similar to useSWR instead of throwing an error or responded with "default" or "fallback" then my app could handle that gracefully. I can update the provider with user ID when I'm ready I can render the provider SSR and sort out the optimizely client when I know it isn't going to throw errors, and the hooks will update when they can based on changes in the provider and fail gracefully if getClient fails. |
Hello All, I'm working to clean up the latent GitHub Issues for the React and Javascript repos. I know we're way behind here and thanks for handing in there with us as we optimize our processes. After reading through this one. I wonder if the Advance Audience Targeting's (AAT) VUID would work to accomplish sticky bucketing for a logged out user. |
We released v3 of the React SDK allowing for anonymous instantiation of the optimizely client without a linked to internal ticket FSSDK-9985 |
How do you handle bucketing for users that are logged out and keep them sticky so they don't see a different variation on each page load?
Optimizely doesn't work unless submit a user id when setting up the provider.
I have an oauth 2.0 pkce login flow so I don't know about a logged in user until the client has access to window.
I'm currently assigning them a random ID, if I have nothing in user state or local storage and saving it to local storage but that is multiple re-renders when using a provider the wraps the entire app on the first visit. it's really nasty.
It would be nice if when no user id was provided Optimizely just handled it on client initialisation with ip/user agent hash or something. So for useDecision stuff for Optimizely full stack can do what Optimizely web can do without user IDs, I can't do this sort of stuff from the client. I could handle a single re-render on log in. This happens automatically for Optimizely web and ironically Optimizely full stack when I'm using flags it doesn't even though it costs a lot more money.
The text was updated successfully, but these errors were encountered: