From 8c653ce879709fc0dd2a0c8775f6f6df9806e282 Mon Sep 17 00:00:00 2001 From: Matchu Date: Wed, 5 Aug 2020 00:25:25 -0700 Subject: [PATCH] memoize Item, clicks on mobile are fast now! This was a surprisingly big win! Item is heavier than it looks, because it has like 6 Chakra components, which aren't expensive but aren't _cheap_ in a re-rendered list that needs to be fast, you know? And it's even more important on search, where there's a lot of items on the page. (we should virtualize it too but that's a thing for another day) --- src/app/WardrobePage/Item.js | 17 +-- src/app/WardrobePage/ItemsPanel.js | 5 +- src/app/WardrobePage/SearchPanel.js | 5 +- src/app/WardrobePage/index.js | 100 ++++++++++-------- .../WardrobePage/support/ItemSupportDrawer.js | 18 ++-- src/app/WardrobePage/useOutfitState.js | 2 + 6 files changed, 84 insertions(+), 63 deletions(-) diff --git a/src/app/WardrobePage/Item.js b/src/app/WardrobePage/Item.js index 139b7d0..94cd82b 100644 --- a/src/app/WardrobePage/Item.js +++ b/src/app/WardrobePage/Item.js @@ -29,12 +29,15 @@ const LoadableItemSupportDrawer = loadable(() => * In fact, this component can't trigger wear or unwear events! When you click * it in the app, you're actually clicking a diff --git a/src/app/WardrobePage/SearchPanel.js b/src/app/WardrobePage/SearchPanel.js index 44a87c6..d37565c 100644 --- a/src/app/WardrobePage/SearchPanel.js +++ b/src/app/WardrobePage/SearchPanel.js @@ -4,7 +4,7 @@ import { Box, Text, VisuallyHidden } from "@chakra-ui/core"; import { useQuery } from "@apollo/client"; import { Delay, Heading1, useDebounce } from "../util"; -import { Item, ItemListContainer, ItemListSkeleton } from "./Item"; +import Item, { ItemListContainer, ItemListSkeleton } from "./Item"; import { itemAppearanceFragment } from "../components/useOutfitAppearance"; /** @@ -173,7 +173,8 @@ function SearchResults({ /> diff --git a/src/app/WardrobePage/index.js b/src/app/WardrobePage/index.js index e1057c6..c7b7e52 100644 --- a/src/app/WardrobePage/index.js +++ b/src/app/WardrobePage/index.js @@ -4,7 +4,7 @@ import loadable from "@loadable/component"; import ItemsAndSearchPanels from "./ItemsAndSearchPanels"; import OutfitPreview from "../components/OutfitPreview"; -import useOutfitState from "./useOutfitState.js"; +import useOutfitState, { OutfitStateContext } from "./useOutfitState.js"; import { usePageTitle } from "../util"; const OutfitControls = loadable(() => @@ -44,58 +44,64 @@ function WardrobePage() { }, [error, toast]); return ( - - + - - - + + + + + + + + - - + - - - - - - + + + ); } diff --git a/src/app/WardrobePage/support/ItemSupportDrawer.js b/src/app/WardrobePage/support/ItemSupportDrawer.js index 81f42e1..9077a9e 100644 --- a/src/app/WardrobePage/support/ItemSupportDrawer.js +++ b/src/app/WardrobePage/support/ItemSupportDrawer.js @@ -28,6 +28,7 @@ import { CheckCircleIcon, EditIcon, ExternalLinkIcon } from "@chakra-ui/icons"; import ItemLayerSupportModal from "./ItemLayerSupportModal"; import { OutfitLayers } from "../../components/OutfitPreview"; import useOutfitAppearance from "../../components/useOutfitAppearance"; +import { OutfitStateContext } from "../useOutfitState"; import useSupportSecret from "./useSupportSecret"; /** @@ -36,7 +37,7 @@ import useSupportSecret from "./useSupportSecret"; * This component controls the drawer element. The actual content is imported * from another lazy-loaded component! */ -function ItemSupportDrawer({ item, outfitState, isOpen, onClose }) { +function ItemSupportDrawer({ item, isOpen, onClose }) { const placement = useBreakpointValue({ base: "bottom", lg: "right", @@ -75,10 +76,7 @@ function ItemSupportDrawer({ item, outfitState, isOpen, onClose }) { - + @@ -231,7 +229,15 @@ function ItemSupportSpecialColorFields({ item }) { ); } -function ItemSupportAppearanceFields({ item, outfitState }) { +/** + * NOTE: This component takes `outfitState` from context, rather than as a prop + * from its parent, for performance reasons. We want `Item` to memoize + * and generally skip re-rendering on `outfitState` changes, and to make + * sure the context isn't accessed when the drawer is closed. So we use + * it here, only when the drawer is open! + */ +function ItemSupportAppearanceFields({ item }) { + const outfitState = React.useContext(OutfitStateContext); const { speciesId, colorId, pose } = outfitState; const { error, visibleLayers } = useOutfitAppearance({ speciesId, diff --git a/src/app/WardrobePage/useOutfitState.js b/src/app/WardrobePage/useOutfitState.js index 2248874..e9ff0a7 100644 --- a/src/app/WardrobePage/useOutfitState.js +++ b/src/app/WardrobePage/useOutfitState.js @@ -7,6 +7,8 @@ import { itemAppearanceFragment } from "../components/useOutfitAppearance"; enableMapSet(); +export const OutfitStateContext = React.createContext(null); + function useOutfitState() { const apolloClient = useApolloClient(); const initialState = parseOutfitUrl();