Skip to content

Commit

Permalink
Add caching layer and other logic to EuiIcon to not fade-in immediate…
Browse files Browse the repository at this point in the history
…ly renderable values
  • Loading branch information
chandlerprall committed Apr 29, 2020
1 parent c9ae5a0 commit b34b2cf
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/components/icon/__snapshots__/icon.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9078,7 +9078,7 @@ exports[`EuiIcon renders custom components 1`] = `
<span
aria-hidden="true"
aria-label="heart"
class="euiIcon euiIcon--medium euiIcon-isLoaded"
class="euiIcon euiIcon--medium"
focusable="false"
role="img"
>
Expand Down
4 changes: 3 additions & 1 deletion src/components/icon/icon.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import { mount } from 'enzyme';
import { requiredProps } from '../../test/required_props';
import cheerio from 'cheerio';

import { EuiIcon, SIZES, TYPES, COLORS } from './icon';
import { EuiIcon, SIZES, TYPES, COLORS, clearIconComponentCache } from './icon';
import { PropsOf } from '../common';

jest.mock('./icon', () => {
return require.requireActual('./icon');
});

beforeEach(() => clearIconComponentCache());

const prettyHtml = cheerio.load('');

function testIcon(props: PropsOf<EuiIcon>) {
Expand Down
50 changes: 41 additions & 9 deletions src/components/icon/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ interface State {
icon: undefined | ComponentType | string;
iconTitle: undefined | string;
isLoading: boolean;
neededLoading: boolean; // controls the fade-in animation, cached icons are immediately rendered
}

function isEuiIconType(x: EuiIconProps['type']): x is EuiIconType {
Expand All @@ -522,6 +523,9 @@ function getInitialIcon(icon: EuiIconProps['type']) {
return undefined;
}
if (isEuiIconType(icon)) {
if (iconComponentCache.hasOwnProperty(icon)) {
return iconComponentCache[icon];
}
return undefined;
}

Expand All @@ -530,6 +534,15 @@ function getInitialIcon(icon: EuiIconProps['type']) {

const generateId = htmlIdGenerator();

let iconComponentCache: { [key: string]: ComponentType } = {};
export const clearIconComponentCache = (icon?: EuiIconType) => {
if (icon != null) {
delete iconComponentCache[icon];
} else {
iconComponentCache = {};
}
};

export class EuiIcon extends PureComponent<EuiIconProps, State> {
isMounted = true;
constructor(props: EuiIconProps) {
Expand All @@ -539,15 +552,18 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
const initialIcon = getInitialIcon(type);
let isLoading = false;

if (isEuiIconType(type)) {
if (isEuiIconType(type) && initialIcon == null) {
isLoading = true;
this.loadIconComponent(type);
} else {
this.onIconLoad();
}

this.state = {
icon: initialIcon,
iconTitle: undefined,
isLoading,
neededLoading: isLoading,
};
}

Expand All @@ -557,13 +573,15 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
if (isEuiIconType(type)) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
neededLoading: iconComponentCache.hasOwnProperty(type),
isLoading: true,
});
this.loadIconComponent(type);
} else {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
icon: type,
neededLoading: true,
isLoading: false,
});
}
Expand All @@ -575,13 +593,25 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
}

loadIconComponent = (iconType: EuiIconType) => {
if (iconComponentCache.hasOwnProperty(iconType)) {
// exists in cache
this.setState({
isLoading: false,
neededLoading: false,
icon: iconComponentCache[iconType],
});
this.onIconLoad();
return;
}

import(
/* webpackChunkName: "icon.[request]" */
// It's important that we don't use a template string here, it
// stops webpack from building a dynamic require context.
// eslint-disable-next-line prefer-template
'./assets/' + typeToPathMap[iconType] + '.js'
).then(({ icon }) => {
iconComponentCache[iconType] = icon;
enqueueStateChange(() => {
if (this.isMounted && this.props.type === iconType) {
this.setState(
Expand All @@ -590,18 +620,20 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
iconTitle: iconType,
isLoading: false,
},
() => {
const { onIconLoad } = this.props;
if (onIconLoad) {
onIconLoad();
}
}
this.onIconLoad
);
}
});
});
};

onIconLoad = () => {
const { onIconLoad } = this.props;
if (onIconLoad) {
onIconLoad();
}
};

render() {
const {
type,
Expand All @@ -614,7 +646,7 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
...rest
} = this.props;

const { isLoading } = this.state;
const { isLoading, neededLoading } = this.state;

let optionalColorClass = null;
let optionalCustomStyles: any = null;
Expand All @@ -640,7 +672,7 @@ export class EuiIcon extends PureComponent<EuiIconProps, State> {
{
'euiIcon--app': isAppIcon,
'euiIcon-isLoading': isLoading,
'euiIcon-isLoaded': !isLoading,
'euiIcon-isLoaded': !isLoading && neededLoading,
},
className
);
Expand Down

0 comments on commit b34b2cf

Please sign in to comment.