From 365e6e35b70f5f5fd36f661475dd6653e050fd97 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 13 Jul 2023 15:18:07 -0700 Subject: [PATCH] Update `EuiProvider` to return early and emit a warning if nested usage is detected --- src/components/provider/provider.test.tsx | 57 +++++++++++++++++++++++ src/components/provider/provider.tsx | 12 ++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/components/provider/provider.test.tsx b/src/components/provider/provider.test.tsx index 3de71b6daab..84fc7a9c96e 100644 --- a/src/components/provider/provider.test.tsx +++ b/src/components/provider/provider.test.tsx @@ -11,6 +11,7 @@ import { render } from '@testing-library/react'; // Note - don't use the EUI cus import { cache as emotionCache } from '@emotion/css'; import createCache from '@emotion/cache'; +import { setEuiDevProviderWarning } from '../../services'; import { EuiProvider } from './provider'; describe('EuiProvider', () => { @@ -155,4 +156,60 @@ describe('EuiProvider', () => { expect(getByText('Dark mode')).toHaveStyleRule('color', '#333'); }); }); + + describe('nested EuiProviders', () => { + it('emits a log/error/warning per `euiDevProviderWarning` levels', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); // Silence warning + setEuiDevProviderWarning('warn'); + + render( + + Top-level provider + Nested + + ); + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + '`EuiProvider` should not be nested or used more than once' + ) + ); + + setEuiDevProviderWarning(undefined); + warnSpy.mockRestore(); + }); + + it('returns children as-is without rendering any nested contexts', () => { + const { container } = render( + + Top-level provider + + Nested + Nested again + + + ); + + expect(container).toMatchInlineSnapshot(` +
+ Top-level provider + Nested + Nested again +
+ `); + }); + + it('does not instantiate any extra logic, including setting cache behavior', () => { + const ignoredCache = createCache({ key: 'ignore' }); + + render( + + Top-level provider + Nested + + ); + + expect(ignoredCache.compat).not.toEqual(true); + }); + }); }); diff --git a/src/components/provider/provider.tsx b/src/components/provider/provider.tsx index 9c98cee5093..ecb35228a87 100644 --- a/src/components/provider/provider.tsx +++ b/src/components/provider/provider.tsx @@ -20,9 +20,10 @@ import { EuiThemeSystem, CurrentEuiBreakpointProvider, } from '../../services'; +import { emitEuiProviderWarning } from '../../services/theme/warning'; import { EuiThemeAmsterdam } from '../../themes'; import { EuiCacheProvider } from './cache'; -import { EuiProviderNestedCheck } from './nested'; +import { EuiProviderNestedCheck, useIsNestedEuiProvider } from './nested'; const isEmotionCacheObject = ( obj: EmotionCache | Object @@ -73,6 +74,15 @@ export const EuiProvider = ({ modify, children, }: PropsWithChildren>) => { + const isNested = useIsNestedEuiProvider(); + if (isNested) { + const providerMessage = `\`EuiProvider\` should not be nested or used more than once, other than at the top level of your app. + Use \`EuiThemeProvider\` instead for nested component-level theming: https://ela.st/euiprovider.`; + + emitEuiProviderWarning(providerMessage); + return children as any; + } + let defaultCache; let globalCache; let utilityCache;