diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a63f0a6b07..0233dfe0b05 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
- Added `euiTextBreakWord()` to `EuiToast` header ([#2549](https://github.com/elastic/eui/pull/2549))
- Fixed `.eui-textBreakAll` on Firefox ([#2549](https://github.com/elastic/eui/pull/2549))
- Fixed `EuiBetaBadge` accessibility with `tab-index=0` ([#2559](https://github.com/elastic/eui/pull/2559))
+- Improved `EuiIcon` loading performance ([#2565](https://github.com/elastic/eui/pull/2565))
## [`16.0.1`](https://github.com/elastic/eui/tree/v16.0.1)
diff --git a/src/components/context_menu/__snapshots__/context_menu.test.tsx.snap b/src/components/context_menu/__snapshots__/context_menu.test.tsx.snap
index d3829f8e2fb..1f7e047d4d1 100644
--- a/src/components/context_menu/__snapshots__/context_menu.test.tsx.snap
+++ b/src/components/context_menu/__snapshots__/context_menu.test.tsx.snap
@@ -114,18 +114,13 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
class="euiContextMenu__itemLayout"
>
+ />
+ />
diff --git a/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap b/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap
index 8ddd71b50f3..b2385514d71 100644
--- a/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap
+++ b/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap
@@ -114,18 +114,13 @@ exports[`EuiContextMenu props panels and initialPanelId allows you to click the
class="euiContextMenu__itemLayout"
>
+ />
+ />
diff --git a/src/components/datagrid/data_grid_inmemory_renderer.tsx b/src/components/datagrid/data_grid_inmemory_renderer.tsx
index 8a8c19ca1df..59b7fef7029 100644
--- a/src/components/datagrid/data_grid_inmemory_renderer.tsx
+++ b/src/components/datagrid/data_grid_inmemory_renderer.tsx
@@ -6,12 +6,13 @@ import React, {
useMemo,
useState,
} from 'react';
-import { createPortal, unstable_batchedUpdates } from 'react-dom';
+import { createPortal } from 'react-dom';
import {
EuiDataGridCellValueElementProps,
EuiDataGridCellProps,
} from './data_grid_cell';
import { EuiDataGridColumn, EuiDataGridInMemory } from './data_grid_types';
+import { enqueueStateChange } from '../../services/react';
interface EuiDataGridInMemoryRendererProps {
inMemory: EuiDataGridInMemory;
@@ -27,27 +28,6 @@ interface EuiDataGridInMemoryRendererProps {
function noop() {}
-const _queue: Function[] = [];
-
-function processQueue() {
- // the queued functions trigger react setStates which, if unbatched,
- // each cause a full update->render->dom pass _per function_
- // instead, tell React to wait until all updates are finished before re-rendering
- unstable_batchedUpdates(() => {
- for (let i = 0; i < _queue.length; i++) {
- _queue[i]();
- }
- _queue.length = 0;
- });
-}
-
-function enqueue(fn: Function) {
- if (_queue.length === 0) {
- setTimeout(processQueue);
- }
- _queue.push(fn);
-}
-
function getElementText(element: HTMLElement) {
return 'innerText' in element
? element.innerText
@@ -71,7 +51,9 @@ const ObservedCell: FunctionComponent<{
onCellRender(i, column, getElementText(ref));
const observer = new MutationObserver(() => {
// onMutation callbacks aren't in the component lifecycle, intentionally batch any effects
- enqueue(onCellRender.bind(null, i, column, getElementText(ref)));
+ enqueueStateChange(
+ onCellRender.bind(null, i, column, getElementText(ref))
+ );
});
observer.observe(ref, {
characterData: true,
diff --git a/src/components/icon/icon.test.tsx b/src/components/icon/icon.test.tsx
index c624075243e..29a8e7bfea7 100644
--- a/src/components/icon/icon.test.tsx
+++ b/src/components/icon/icon.test.tsx
@@ -10,14 +10,14 @@ const prettyHtml = cheerio.load('');
function testIcon(props: PropsOf) {
return () => {
- const component = mount();
-
+ expect.assertions(1);
return new Promise(resolve => {
- setTimeout(() => {
+ const onIconLoad = () => {
component.update();
expect(prettyHtml(component.html())).toMatchSnapshot();
resolve();
- }, 0);
+ };
+ const component = mount();
});
};
}
diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index 7e7158bf103..bcbf82293ec 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -15,6 +15,7 @@ import { CommonProps, keysOf } from '../common';
// TS file (dev/docs) or the JS file (distributed), and it's more effort than worth
// to generate & git track a TS module definition for each icon component
import { icon as empty } from './assets/empty.js';
+import { enqueueStateChange } from '../../services/react';
const typeToPathMap = {
addDataApp: 'app_add_data',
@@ -428,6 +429,10 @@ export type EuiIconProps = CommonProps &
* Note that every size other than `original` assumes the provided SVG sits on a square viewbox.
*/
size?: IconSize;
+ /**
+ * Callback when the icon has been loaded & rendered
+ */
+ onIconLoad?: () => void;
};
interface State {
@@ -500,12 +505,22 @@ export class EuiIcon extends PureComponent {
// eslint-disable-next-line prefer-template
'./assets/' + typeToPathMap[iconType] + '.js'
).then(({ icon }) => {
- if (this.isMounted) {
- this.setState({
- icon,
- isLoading: false,
- });
- }
+ enqueueStateChange(() => {
+ if (this.isMounted) {
+ this.setState(
+ {
+ icon,
+ isLoading: false,
+ },
+ () => {
+ const { onIconLoad } = this.props;
+ if (onIconLoad) {
+ onIconLoad();
+ }
+ }
+ );
+ }
+ });
});
};
@@ -516,6 +531,7 @@ export class EuiIcon extends PureComponent {
color,
className,
tabIndex,
+ onIconLoad,
...rest
} = this.props;
diff --git a/src/services/react.ts b/src/services/react.ts
new file mode 100644
index 00000000000..43492511228
--- /dev/null
+++ b/src/services/react.ts
@@ -0,0 +1,22 @@
+import { unstable_batchedUpdates } from 'react-dom';
+
+const _queue: Function[] = [];
+
+function processQueue() {
+ // the queued functions trigger react setStates which, if unbatched,
+ // each cause a full update->render->dom pass _per function_
+ // instead, tell React to wait until all updates are finished before re-rendering
+ unstable_batchedUpdates(() => {
+ for (let i = 0; i < _queue.length; i++) {
+ _queue[i]();
+ }
+ _queue.length = 0;
+ });
+}
+
+export function enqueueStateChange(fn: Function) {
+ if (_queue.length === 0) {
+ setTimeout(processQueue);
+ }
+ _queue.push(fn);
+}