From 44398e20393a60dae6074320e8e51fd565e5b932 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 12 Jan 2024 13:22:30 +0100 Subject: [PATCH] significantly improve render performance on nav hover --- src/components/MobileNav.js | 50 ++++++----- src/components/Page.js | 23 +++-- src/components/PageLayout/PageLayout.js | 62 ++++++++------ src/components/PageWidthContext.js | 31 ++++--- src/components/Sidebar/DefaultSidebarNav.js | 16 ++-- src/components/Sidebar/Sidebar.js | 49 ++++++----- src/components/Sidebar/SidebarNav.js | 15 ++-- src/utils/config.js | 95 +++++++++++---------- src/utils/index.js | 61 ++++++------- 9 files changed, 228 insertions(+), 174 deletions(-) diff --git a/src/components/MobileNav.js b/src/components/MobileNav.js index 869712410..41a056e9f 100644 --- a/src/components/MobileNav.js +++ b/src/components/MobileNav.js @@ -1,6 +1,6 @@ import AuthCheck from './AuthCheck'; import PropTypes from 'prop-types'; -import React, {useContext, useState} from 'react'; +import React, {useContext, useMemo, useState} from 'react'; import { Box, Drawer, @@ -21,26 +21,30 @@ function MobileNavContent({defaultActiveDocset}) { const [activeDocset, setActiveDocset] = useState( defaultActiveDocset || basePath ); + const pathContextValue = useMemo( + () => ({ + ...pathContext, + basePath: `/${activeDocset}` + }), + [activeDocset, pathContext] + ); + const docsetContextValue = useMemo( + () => ({ + configs, + activeDocset, + setActiveDocset, + sidebarOpen: true, + clickToSelect: true + }), + [activeDocset, configs] + ); return ( <> {!activeDocset && } - + {activeDocset ? ( - + ({ + ...pathContext, + basePath: '/' + }), + [pathContext] + ); return ( <> + } diff --git a/src/components/Page.js b/src/components/Page.js index b00734d86..1219a6488 100644 --- a/src/components/Page.js +++ b/src/components/Page.js @@ -493,15 +493,20 @@ export default function Page({file}) { ) : null } > - - {childMdx ? ( - - {childMdx.body} - - ) : ( - processSync(childMarkdownRemark.html).result - )} - + {useMemo( + () => ( + + {childMdx ? ( + + {childMdx.body} + + ) : ( + processSync(childMarkdownRemark.html).result + )} + + ), + [childMarkdownRemark?.html, childMdx, language, setLanguage] + )} setSidebarLocked(!sidebarLocked) - }; + const lockProps = useMemo( + () => ({ + isLocked: sidebarLocked, + onLockToggle: () => setSidebarLocked(!sidebarLocked) + }), + [setSidebarLocked, sidebarLocked] + ); - const hideSidebar = () => setSidebarHidden(true); + const hideSidebar = useCallback( + () => setSidebarHidden(true), + [setSidebarHidden] + ); const {pathname} = location; const {basePath = '/'} = pageContext; @@ -44,18 +50,21 @@ export function PageLayout({pageContext, children, location, data}) { const {docset, versions, currentVersion, navItems, algoliaFilters, internal} = useConfig(basePath); - const sidebarNav = ( - { - navigate(`/${version.slug}`); - }} - {...lockProps} - /> + const sidebarNav = useMemo( + () => ( + { + navigate(`/${version.slug}`); + }} + {...lockProps} + /> + ), + [currentVersion, docset, hideSidebar, lockProps, navItems, versions] ); const [now, setNow] = useState(Date.now()); @@ -66,15 +75,18 @@ export function PageLayout({pageContext, children, location, data}) { signupTracer('docs'); }, []); + const pathContextValue = useMemo( + () => ({ + uri: pathname, + basePath, + path: data?.file?.name === 'index' ? pathname : dirname(pathname) + }), + [basePath, data?.file?.name, pathname] + ); + return ( - +
diff --git a/src/components/PageWidthContext.js b/src/components/PageWidthContext.js index 5ec6b3c97..88300251b 100644 --- a/src/components/PageWidthContext.js +++ b/src/components/PageWidthContext.js @@ -4,6 +4,7 @@ import React, { useCallback, useContext, useEffect, + useMemo, useRef, useState } from 'react'; @@ -117,18 +118,28 @@ export const PageWidthProvider = ({children}) => { root.style.setProperty(DOCS_PAGE_WIDTH_VAR, pageWidthPx + 'px'); }, [pageWidthPx]); + const pageWidthContextValue = useMemo( + () => ({ + pageWidthPx, + pageWidth, + setPageWidth, + togglePageWidth, + pageRefCallback, + showExpandButton + }), + [ + pageRefCallback, + pageWidth, + pageWidthPx, + setPageWidth, + showExpandButton, + togglePageWidth + ] + ); + // Create a context provider with values return ( - + {children} ); diff --git a/src/components/Sidebar/DefaultSidebarNav.js b/src/components/Sidebar/DefaultSidebarNav.js index 86660ca6d..dc8eaf781 100644 --- a/src/components/Sidebar/DefaultSidebarNav.js +++ b/src/components/Sidebar/DefaultSidebarNav.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useContext} from 'react'; +import React, {useContext, useMemo} from 'react'; import SidebarNav from './SidebarNav'; import {PathContext} from '../../utils'; import {useConfig} from '../../utils/config'; @@ -7,13 +7,15 @@ import {useConfig} from '../../utils/config'; export const DefaultSidebarNav = ({hideSidebar, isLocked, onLockToggle}) => { const {docset, navItems} = useConfig('/'); const pathContext = useContext(PathContext); + const pathContextValue = useMemo( + () => ({ + ...pathContext, + basePath: '/' + }), + [pathContext] + ); return ( - + ({ + ...pathContext, + basePath: `/${activeDocset}` + }), + [pathContext, activeDocset] + ); + + const docsetContextValue = useMemo( + () => ({ + configs, + activeDocset, + setActiveDocset, + sidebarOpen, + openSidebar, + dismissSidebar, + onKeyboardSelect: () => { + setSidebarOpen(false); + sidebarNavRef.current?.focusFirstLink(); + }, + isLocked + }), + [activeDocset, configs, dismissSidebar, isLocked, openSidebar, sidebarOpen] + ); + return ( - { - setSidebarOpen(false); - sidebarNavRef.current?.focusFirstLink(); - }, - isLocked - }} - > + {activeDocset ? ( - + ({ + nav, + setNav: setLocalNavState + }), + [nav, setLocalNavState] + ); + return ( )} - + diff --git a/src/utils/config.js b/src/utils/config.js index df4d858ba..c44db74f7 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -1,4 +1,5 @@ import {graphql, useStaticQuery} from 'gatsby'; +import {useMemo} from 'react'; import {v5} from 'uuid'; // turn a sidebar configuration object to an array of nav items @@ -72,53 +73,57 @@ export const useConfigs = () => { } `); - return data.configs.nodes.reduce((acc, config, _, nodes) => { - // TODO: convert configs to YAML - const content = JSON.parse(config.fields.content); - const { - title, - link, - version, - sidebar, - algoliaFilters, - internal, - versionBanner - } = content; - - const slug = configToSlug(config); - const parent = getParentDocset(slug); - - const versions = []; - - for (const node of nodes) { - const nodeSlug = configToSlug(node); - if (getParentDocset(nodeSlug) === parent) { - const {version} = JSON.parse(node.fields.content); - if (version) { - versions.push({ - label: version, - slug: nodeSlug - }); + return useMemo( + () => + data.configs.nodes.reduce((acc, config, _, nodes) => { + // TODO: convert configs to YAML + const content = JSON.parse(config.fields.content); + const { + title, + link, + version, + sidebar, + algoliaFilters, + internal, + versionBanner + } = content; + + const slug = configToSlug(config); + const parent = getParentDocset(slug); + + const versions = []; + + for (const node of nodes) { + const nodeSlug = configToSlug(node); + if (getParentDocset(nodeSlug) === parent) { + const {version} = JSON.parse(node.fields.content); + if (version) { + versions.push({ + label: version, + slug: nodeSlug + }); + } + } } - } - } - versions.sort((a, b) => b.label.localeCompare(a.label)); - - return { - ...acc, - [slug]: { - docset: title, - currentVersion: version, - navItems: getNavItems(sidebar), - algoliaFilters, - internal, - versions, - versionBanner, - link - } - }; - }, {}); + versions.sort((a, b) => b.label.localeCompare(a.label)); + + return { + ...acc, + [slug]: { + docset: title, + currentVersion: version, + navItems: getNavItems(sidebar), + algoliaFilters, + internal, + versions, + versionBanner, + link + } + }; + }, {}), + [data] + ); }; export const useConfig = slug => { diff --git a/src/utils/index.js b/src/utils/index.js index b1b87f826..5bb6d15fc 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,4 +1,4 @@ -import {createContext} from 'react'; +import {createContext, useMemo} from 'react'; import {gql, useQuery} from '@apollo/client'; import {join, relative} from 'path'; import {useColorModeValue} from '@chakra-ui/react'; @@ -25,40 +25,43 @@ export const flattenNavItems = items => export function useFieldTableStyles() { const teal = useColorModeValue('teal.600', 'teal.300'); const requiredBg = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - return { - 'tr.required': { - bg: requiredBg - }, - td: { - ':first-child': { - [['h5', 'h6']]: { - mb: 1, - fontSize: 'md', - fontWeight: 'normal' - }, - p: { - fontSize: 'sm', + return useMemo( + () => ({ + 'tr.required': { + bg: requiredBg + }, + td: { + ':first-child': { + [['h5', 'h6']]: { + mb: 1, + fontSize: 'md', + fontWeight: 'normal' + }, + p: { + fontSize: 'sm', + code: { + p: 0, + bg: 'none', + color: teal + } + }, code: { - p: 0, - bg: 'none', - color: teal + fontSize: 'inherit' } }, - code: { - fontSize: 'inherit' - } - }, - ':not(:first-child)': { - lineHeight: 'base', - fontSize: 'md', - p: { - ':not(:last-child)': { - mb: 2 + ':not(:first-child)': { + lineHeight: 'base', + fontSize: 'md', + p: { + ':not(:last-child)': { + mb: 2 + } } } } - } - }; + }), + [requiredBg, teal] + ); } const GET_USER = gql`