From fb54ac429bcfbc4dbe03019ec701196108504b77 Mon Sep 17 00:00:00 2001 From: Nick Santos Date: Tue, 22 Feb 2022 13:59:02 -0500 Subject: [PATCH] web: memoize resource selection provider --- web/src/ResourceSelectionContext.test.tsx | 31 +++++++++++- web/src/ResourceSelectionContext.tsx | 57 +++++++++++++---------- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/web/src/ResourceSelectionContext.test.tsx b/web/src/ResourceSelectionContext.test.tsx index 103fe65909..81cff1058f 100644 --- a/web/src/ResourceSelectionContext.test.tsx +++ b/web/src/ResourceSelectionContext.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react" +import { act, render, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" import React, { ChangeEvent, useState } from "react" import { @@ -157,4 +157,33 @@ describe("ResourceSelectionContext", () => { expect(selectedState()).toBe(JSON.stringify([])) }) }) + + it("memoizes renders", () => { + let renderCount = 0 + let selection: any + let FakeEl = React.memo(() => { + selection = useResourceSelection() + renderCount++ + return
+ }) + + let tree = () => { + return ( + + + + ) + } + + let { rerender } = render(tree()) + + expect(renderCount).toEqual(1) + rerender(tree()) + + // Make sure we don't re-render on a no-op update. + expect(renderCount).toEqual(1) + + act(() => selection.clearSelections()) + expect(renderCount).toEqual(2) + }) }) diff --git a/web/src/ResourceSelectionContext.tsx b/web/src/ResourceSelectionContext.tsx index 54ed2a4b97..f932602e24 100644 --- a/web/src/ResourceSelectionContext.tsx +++ b/web/src/ResourceSelectionContext.tsx @@ -1,4 +1,10 @@ -import { createContext, PropsWithChildren, useContext, useState } from "react" +import { + createContext, + PropsWithChildren, + useContext, + useMemo, + useState, +} from "react" /** * The ResourceSelection state keeps track of what resources are selected for bulk actions to be performed on them. @@ -41,33 +47,34 @@ export function ResourceSelectionProvider( const selections = new Set(props.initialValuesForTesting) || new Set() const [selectedResources, setSelectedResources] = useState(selections) - function isSelected(resourceName: string) { - return selectedResources.has(resourceName) - } + const contextValue: ResourceSelectionContext = useMemo(() => { + function isSelected(resourceName: string) { + return selectedResources.has(resourceName) + } - function select(...resourceNames: string[]) { - const newSelections = new Set(selectedResources) - resourceNames.forEach((name) => newSelections.add(name)) - return setSelectedResources(newSelections) - } + function select(...resourceNames: string[]) { + const newSelections = new Set(selectedResources) + resourceNames.forEach((name) => newSelections.add(name)) + return setSelectedResources(newSelections) + } - function deselect(...resourceNames: string[]) { - const newSelections = new Set(selectedResources) - resourceNames.forEach((name) => newSelections.delete(name)) - return setSelectedResources(newSelections) - } + function deselect(...resourceNames: string[]) { + const newSelections = new Set(selectedResources) + resourceNames.forEach((name) => newSelections.delete(name)) + return setSelectedResources(newSelections) + } - function clearSelections() { - setSelectedResources(new Set()) - } - - const contextValue: ResourceSelectionContext = { - selected: selectedResources, - isSelected, - select, - deselect, - clearSelections, - } + function clearSelections() { + setSelectedResources(new Set()) + } + return { + selected: selectedResources, + isSelected, + select, + deselect, + clearSelections, + } + }, [selectedResources, setSelectedResources]) return (