diff --git a/packages/react/src/context/use-context-mutator.ts b/packages/react/src/context/use-context-mutator.ts index 8ab11ac47..8e67e7917 100644 --- a/packages/react/src/context/use-context-mutator.ts +++ b/packages/react/src/context/use-context-mutator.ts @@ -1,4 +1,4 @@ -import { useCallback, useContext, useRef } from 'react'; +import { useCallback, useContext } from 'react'; import type { EvaluationContext } from '@openfeature/web-sdk'; import { OpenFeature } from '@openfeature/web-sdk'; import { Context } from '../internal'; @@ -32,20 +32,19 @@ export type ContextMutation = { */ export function useContextMutator(options: ContextMutationOptions = { defaultContext: false }): ContextMutation { const { domain } = useContext(Context) || {}; - const previousContext = useRef(null); const setContext = useCallback(async (updatedContext: EvaluationContext | ((currentContext: EvaluationContext) => EvaluationContext)): Promise => { + const previousContext = OpenFeature.getContext(options?.defaultContext ? undefined : domain); const resolvedContext = typeof updatedContext === 'function' - ? updatedContext(OpenFeature.getContext(options?.defaultContext ? undefined : domain)) + ? updatedContext(previousContext) : updatedContext; - if (previousContext.current !== resolvedContext) { + if (previousContext !== resolvedContext) { if (!domain || options?.defaultContext) { OpenFeature.setContext(resolvedContext); } else { OpenFeature.setContext(domain, resolvedContext); } - previousContext.current = resolvedContext; } }, [domain, options?.defaultContext]); diff --git a/packages/react/test/provider.spec.tsx b/packages/react/test/provider.spec.tsx index b5f0731b3..bda795aee 100644 --- a/packages/react/test/provider.spec.tsx +++ b/packages/react/test/provider.spec.tsx @@ -1,5 +1,5 @@ import type { EvaluationContext} from '@openfeature/web-sdk'; -import { InMemoryProvider, OpenFeature } from '@openfeature/web-sdk'; +import { InMemoryProvider, OpenFeature, ProviderEvents } from '@openfeature/web-sdk'; import '@testing-library/jest-dom'; // see: https://testing-library.com/docs/react-testing-library/setup import { render, renderHook, screen, waitFor, fireEvent, act } from '@testing-library/react'; import * as React from 'react'; @@ -306,36 +306,50 @@ describe('OpenFeatureProvider', () => { expect(screen.getByText('Will says aloha')).toBeInTheDocument(); }); - it('should accept a method taking the previous context', async () => { + it('should accept a method taking the previous context', () => { const DOMAIN = 'mutate-context-with-function'; OpenFeature.setProvider(DOMAIN, suspendingProvider(), { done: false }); + const reconcile = jest.fn(); + OpenFeature.getClient(DOMAIN).addHandler(ProviderEvents.Reconciling, reconcile); + const setter = jest.fn((prevContext: EvaluationContext) => ({ ...prevContext, user: 'bob@flags.com' })); render( - {FALLBACK}}> - - + , ); - await waitFor(() => { - expect(screen.getByText('Will says hi')).toBeInTheDocument(); - }); - act(() => { fireEvent.click(screen.getByText('Update Context')); }); - await waitFor( - () => { - expect(screen.getByText('Will says aloha')).toBeInTheDocument(); - }, - { timeout: DELAY * 4 }, + expect(setter).toHaveBeenCalledTimes(1); + expect(setter).toHaveBeenCalledWith({ done: false }); + expect(reconcile).toHaveBeenCalledTimes(1); + expect(OpenFeature.getContext(DOMAIN)).toEqual({ done: false, user: 'bob@flags.com' }); + }); + + it('should noop if the previous context is passed in unchanged', () => { + const DOMAIN = 'mutate-context-noop'; + OpenFeature.setProvider(DOMAIN, suspendingProvider(), { done: false }); + + const reconcile = jest.fn(); + OpenFeature.getClient(DOMAIN).addHandler(ProviderEvents.Reconciling, reconcile); + + const setter = jest.fn((prevContext: EvaluationContext) => prevContext); + render( + + + , ); + act(() => { + fireEvent.click(screen.getByText('Update Context')); + }); expect(setter).toHaveBeenCalledTimes(1); expect(setter).toHaveBeenCalledWith({ done: false }); - expect(OpenFeature.getContext(DOMAIN)).toEqual({ done: false, user: 'bob@flags.com' }); + expect(reconcile).toHaveBeenCalledTimes(0); + expect(OpenFeature.getContext(DOMAIN)).toEqual({ done: false }); }); }); });