Skip to content

Commit

Permalink
Update EuiProvider to return early and emit a warning if nested usa…
Browse files Browse the repository at this point in the history
…ge is detected
  • Loading branch information
cee-chen committed Jul 13, 2023
1 parent 73bf66c commit 365e6e3
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
57 changes: 57 additions & 0 deletions src/components/provider/provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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(
<EuiProvider>
Top-level provider
<EuiProvider>Nested</EuiProvider>
</EuiProvider>
);

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(
<EuiProvider>
Top-level provider
<EuiProvider>
Nested
<EuiProvider>Nested again</EuiProvider>
</EuiProvider>
</EuiProvider>
);

expect(container).toMatchInlineSnapshot(`
<div>
Top-level provider
Nested
Nested again
</div>
`);
});

it('does not instantiate any extra logic, including setting cache behavior', () => {
const ignoredCache = createCache({ key: 'ignore' });

render(
<EuiProvider>
Top-level provider
<EuiProvider cache={ignoredCache}>Nested</EuiProvider>
</EuiProvider>
);

expect(ignoredCache.compat).not.toEqual(true);
});
});
});
12 changes: 11 additions & 1 deletion src/components/provider/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,6 +74,15 @@ export const EuiProvider = <T extends {} = {}>({
modify,
children,
}: PropsWithChildren<EuiProviderProps<T>>) => {
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;
Expand Down

0 comments on commit 365e6e3

Please sign in to comment.