perf: avoid uneccessary notes list item rerenders (#1904)
This commit is contained in:
@@ -40,6 +40,7 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
const { selectPreviousItem, selectNextItem } = selectionController
|
||||
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions
|
||||
const { sortBy } = itemListController.displayOptions
|
||||
const selectedTag = navigationController.selected
|
||||
|
||||
const onScroll: UIEventHandler = useCallback(
|
||||
(e) => {
|
||||
@@ -72,25 +73,27 @@ const ContentList: FunctionComponent<Props> = ({
|
||||
[selectionController],
|
||||
)
|
||||
|
||||
const getTagsForItem = (item: ListableContentItem) => {
|
||||
if (hideTags) {
|
||||
return []
|
||||
}
|
||||
const getTagsForItem = useCallback(
|
||||
(item: ListableContentItem) => {
|
||||
if (hideTags) {
|
||||
return []
|
||||
}
|
||||
|
||||
const selectedTag = navigationController.selected
|
||||
if (!selectedTag) {
|
||||
return []
|
||||
}
|
||||
if (!selectedTag) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tags = application.getItemTags(item)
|
||||
const tags = application.getItemTags(item)
|
||||
|
||||
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
|
||||
if (isNavigatingOnlyTag) {
|
||||
return []
|
||||
}
|
||||
const isNavigatingOnlyTag = selectedTag instanceof SNTag && tags.length === 1
|
||||
if (isNavigatingOnlyTag) {
|
||||
return []
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
return tags
|
||||
},
|
||||
[hideTags, selectedTag, application],
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ContentType } from '@standardnotes/snjs'
|
||||
import { FunctionComponent } from 'react'
|
||||
import React, { FunctionComponent } from 'react'
|
||||
import FileListItem from './FileListItem'
|
||||
import NoteListItem from './NoteListItem'
|
||||
import { AbstractListItemProps } from './Types/AbstractListItemProps'
|
||||
import { AbstractListItemProps, doListItemPropsMeritRerender } from './Types/AbstractListItemProps'
|
||||
|
||||
const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
|
||||
switch (props.item.content_type) {
|
||||
@@ -15,4 +15,4 @@ const ContentListItem: FunctionComponent<AbstractListItemProps> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
export default ContentListItem
|
||||
export default React.memo(ContentListItem, (a, b) => !doListItemPropsMeritRerender(a, b))
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||
import { useContextMenuEvent } from '@/Hooks/useContextMenuEvent'
|
||||
import ListItemNotePreviewText from './ListItemNotePreviewText'
|
||||
import { ListItemTitle } from './ListItemTitle'
|
||||
import { log, LoggingDomain } from '@/Logging'
|
||||
|
||||
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
application,
|
||||
@@ -70,6 +71,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||
|
||||
useContextMenuEvent(listItemRef, openContextMenu)
|
||||
|
||||
log(LoggingDomain.ItemsList, 'Rendering note list item', item.title)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={listItemRef}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { FilesController } from '@/Controllers/FilesController'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { SortableItem, SNTag } from '@standardnotes/snjs'
|
||||
import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
|
||||
import { ListableContentItem } from './ListableContentItem'
|
||||
|
||||
export type AbstractListItemProps = {
|
||||
@@ -18,3 +18,89 @@ export type AbstractListItemProps = {
|
||||
sortBy: keyof SortableItem | undefined
|
||||
tags: SNTag[]
|
||||
}
|
||||
|
||||
export function doListItemPropsMeritRerender(previous: AbstractListItemProps, next: AbstractListItemProps): boolean {
|
||||
const simpleComparison: (keyof AbstractListItemProps)[] = [
|
||||
'onSelect',
|
||||
'hideDate',
|
||||
'hideIcon',
|
||||
'hideTags',
|
||||
'hidePreview',
|
||||
'selected',
|
||||
'sortBy',
|
||||
]
|
||||
|
||||
for (const key of simpleComparison) {
|
||||
if (previous[key] !== next[key]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (previous['item'] !== next['item']) {
|
||||
if (doesItemChangeMeritRerender(previous['item'], next['item'])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return doesTagsChangeMeritRerender(previous['tags'], next['tags'])
|
||||
}
|
||||
|
||||
function doesTagsChangeMeritRerender(previous: SNTag[], next: SNTag[]): boolean {
|
||||
if (previous === next) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (previous.length !== next.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (previous.length === 0 && next.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Uuids(previous).sort().join() !== Uuids(next).sort().join()) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
previous
|
||||
.map((t) => t.title)
|
||||
.sort()
|
||||
.join() !==
|
||||
next
|
||||
.map((t) => t.title)
|
||||
.sort()
|
||||
.join()
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function doesItemChangeMeritRerender(previous: ListableContentItem, next: ListableContentItem): boolean {
|
||||
if (previous.uuid !== next.uuid) {
|
||||
return true
|
||||
}
|
||||
|
||||
const propertiesMeritingRerender: (keyof ListableContentItem)[] = [
|
||||
'title',
|
||||
'protected',
|
||||
'updatedAtString',
|
||||
'createdAtString',
|
||||
'hidePreview',
|
||||
'preview_html',
|
||||
'preview_plain',
|
||||
'archived',
|
||||
'starred',
|
||||
'pinned',
|
||||
]
|
||||
|
||||
for (const key of propertiesMeritingRerender) {
|
||||
if (previous[key] !== next[key]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user