AppChrome: Reduce re-renders for identical pageNavs (#62483)

* AppChrome: Reduce re-renders for identical pageNavs

* Update

* removed log
This commit is contained in:
Torkel Ödegaard 2023-02-07 08:01:22 +01:00 committed by GitHub
parent b1e58eb47e
commit 39537043ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 2 deletions

View File

@ -7,4 +7,33 @@ describe('AppChromeService', () => {
chromeService.onToggleKioskMode();
expect(chromeService.state.getValue().chromeless).toBe(true);
});
it('Ignore state updates when sectionNav and pageNav have new instance but same text, url or active child', () => {
const chromeService = new AppChromeService();
let stateChanges = 0;
chromeService.state.subscribe(() => stateChanges++);
chromeService.update({ sectionNav: { text: 'hello' }, pageNav: { text: 'test', url: 'A' } });
chromeService.update({ sectionNav: { text: 'hello' }, pageNav: { text: 'test', url: 'A' } });
expect(stateChanges).toBe(2);
// if url change we should update
chromeService.update({ sectionNav: { text: 'hello' }, pageNav: { text: 'test', url: 'new/url' } });
expect(stateChanges).toBe(3);
// if active child changed should update state
chromeService.update({
sectionNav: { text: 'hello' },
pageNav: { text: 'test', url: 'A', children: [{ text: 'child', active: true }] },
});
expect(stateChanges).toBe(4);
// If active child is the same we should not update state
chromeService.update({
sectionNav: { text: 'hello' },
pageNav: { text: 'test', url: 'A', children: [{ text: 'child', active: true }] },
});
expect(stateChanges).toBe(4);
});
});

View File

@ -24,7 +24,7 @@ export interface AppChromeState {
export class AppChromeService {
searchBarStorageKey = 'SearchBar_Hidden';
private currentRoute?: RouteDescriptor;
private routeChangeHandled?: boolean;
private routeChangeHandled = true;
readonly state = new BehaviorSubject<AppChromeState>({
chromeless: true, // start out hidden to not flash it on pages without chrome
@ -60,11 +60,29 @@ export class AppChromeService {
// KioskMode overrides chromeless state
newState.chromeless = newState.kioskMode === KioskMode.Full || this.currentRoute?.chromeless;
if (!isShallowEqual(current, newState)) {
if (!this.ignoreStateUpdate(newState, current)) {
this.state.next(newState);
}
}
ignoreStateUpdate(newState: AppChromeState, current: AppChromeState) {
if (isShallowEqual(newState, current)) {
return true;
}
// Some updates can have new instance of sectionNav or pageNav but with same values
if (newState.sectionNav !== current.sectionNav || newState.pageNav !== current.pageNav) {
if (
navItemsAreTheSame(newState.sectionNav, current.sectionNav) &&
navItemsAreTheSame(newState.pageNav, current.pageNav)
) {
return true;
}
}
return false;
}
useState() {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useObservable(this.state, this.state.getValue());
@ -134,3 +152,17 @@ export class AppChromeService {
return null;
}
}
/**
* Checks if text, url and active child url are the same
**/
function navItemsAreTheSame(a: NavModelItem | undefined, b: NavModelItem | undefined) {
if (a === b) {
return true;
}
const aActiveChild = a?.children?.find((child) => child.active);
const bActiveChild = b?.children?.find((child) => child.active);
return a?.text === b?.text && a?.url === b?.url && aActiveChild?.url === bActiveChild?.url;
}