1717import React , { createContext , useRef , useMemo , useEffect } from 'react' ;
1818
1919import { ProviderStateStore } from './ProviderStateStore' ;
20- import type { OptimizelyProviderProps , OptimizelyContextValue } from './types' ;
20+ import { UserContextManager } from '../utils/UserContextManager' ;
21+ import { areUsersEqual , areSegmentsEqual } from '../utils/helpers' ;
22+ import type { OptimizelyProviderProps , OptimizelyContextValue , UserInfo } from './types' ;
2123
2224// TODO: Replace with proper logger when implemented
2325const logger = {
@@ -36,10 +38,13 @@ export function OptimizelyProvider({
3638 user,
3739 timeout,
3840 skipSegments = false ,
41+ qualifiedSegments,
3942 children,
4043} : OptimizelyProviderProps ) : React . ReactElement {
4144 const storeRef = useRef < ProviderStateStore | null > ( null ) ;
42- // Todo: const prevUserRef = useRef<UserInfo | undefined>(undefined);
45+ const managerRef = useRef < UserContextManager | null > ( null ) ;
46+ const prevUserRef = useRef < UserInfo | undefined > ( undefined ) ;
47+ const prevSegmentsRef = useRef < string [ ] | undefined > ( undefined ) ;
4348
4449 if ( storeRef . current === null ) {
4550 storeRef . current = new ProviderStateStore ( ) ;
@@ -52,7 +57,7 @@ export function OptimizelyProvider({
5257 store,
5358 client,
5459 } ) ,
55- [ store , client ]
60+ [ client , store ]
5661 ) ;
5762
5863 useEffect ( ( ) => {
@@ -88,10 +93,48 @@ export function OptimizelyProvider({
8893 } ;
8994 } , [ client , timeout , store ] ) ;
9095
91- // Handle user changes
96+ // Effect 2: Manager lifecycle (create/dispose when client/skipSegments changes)
97+ // Does NOT trigger createUserContext — only manages the manager instance
9298 useEffect ( ( ) => {
93- // TODO: UserContextManager implementation
94- } , [ ] ) ;
99+ if ( ! client ) return ;
100+
101+ managerRef . current ?. dispose ( ) ;
102+ managerRef . current = new UserContextManager ( {
103+ client,
104+ skipSegments,
105+ onUserContextReady : ( ctx ) => store . setUserContext ( ctx ) ,
106+ onError : ( error ) => store . setError ( error ) ,
107+ } ) ;
108+
109+ // Reset prevUser/segments so Effect 3 treats current user as new
110+ prevUserRef . current = undefined ;
111+ prevSegmentsRef . current = undefined ;
112+
113+ return ( ) => {
114+ managerRef . current ?. dispose ( ) ;
115+ managerRef . current = null ;
116+ } ;
117+ } , [ client , skipSegments , store ] ) ;
118+
119+ // Effect 3: User/segments prop changes — sole trigger for createUserContext
120+ // Runs on mount (prevUser is undefined) and on user/qualifiedSegments prop changes.
121+ // Also re-runs when client/skipSegments change (because Effect 2 resets
122+ // prevUserRef/prevSegmentsRef to undefined, and these deps are shared).
123+ useEffect ( ( ) => {
124+ if ( ! managerRef . current ) return ;
125+
126+ const prevUser = prevUserRef . current ;
127+ const prevSegments = prevSegmentsRef . current ;
128+ const userChanged = prevUser === undefined || ! areUsersEqual ( prevUser , user ) ;
129+ const segmentsChanged = ! areSegmentsEqual ( prevSegments , qualifiedSegments ) ;
130+
131+ if ( ! userChanged && ! segmentsChanged ) return ;
132+
133+ prevUserRef . current = user ;
134+ prevSegmentsRef . current = qualifiedSegments ;
135+ managerRef . current . createUserContext ( user , qualifiedSegments ) ;
136+ // eslint-disable-next-line react-hooks/exhaustive-deps
137+ } , [ user ?. id , user ?. attributes , qualifiedSegments , client , skipSegments ] ) ;
95138
96139 // Cleanup on unmount
97140 useEffect ( ( ) => {
0 commit comments