From 2e3f3666b5340b8eb778104a1d4a3f4d52be6528 Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Tue, 24 Aug 2021 10:10:58 +1000 Subject: [PATCH] Improve performance of create item modal with many fields (#6390) --- .changeset/clean-turtles-rule.md | 5 ++ .../admin-ui/components/CreateItemDrawer.tsx | 64 ++++++++----------- .../keystone/src/scripts/tests/build.test.ts | 29 ++------- 3 files changed, 37 insertions(+), 61 deletions(-) create mode 100644 .changeset/clean-turtles-rule.md diff --git a/.changeset/clean-turtles-rule.md b/.changeset/clean-turtles-rule.md new file mode 100644 index 00000000000..2b71f5b9a7c --- /dev/null +++ b/.changeset/clean-turtles-rule.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/keystone': patch +--- + +Improved performance of create item modal with many fields diff --git a/packages/keystone/src/admin-ui/components/CreateItemDrawer.tsx b/packages/keystone/src/admin-ui/components/CreateItemDrawer.tsx index 7cf0fd219ee..8928c4bfb84 100644 --- a/packages/keystone/src/admin-ui/components/CreateItemDrawer.tsx +++ b/packages/keystone/src/admin-ui/components/CreateItemDrawer.tsx @@ -1,8 +1,8 @@ /* @jsx jsx */ -import { useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import isDeepEqual from 'fast-deep-equal'; -import { jsx, Stack } from '@keystone-ui/core'; +import { jsx, Box } from '@keystone-ui/core'; import { Drawer } from '@keystone-ui/modals'; import { useToasts } from '@keystone-ui/toast'; import { LoadingDots } from '@keystone-ui/loading'; @@ -10,8 +10,11 @@ import { LoadingDots } from '@keystone-ui/loading'; import { gql, useMutation } from '../apollo'; import { useKeystone, useList } from '../context'; +import { Fields } from '../utils/Fields'; import { GraphQLErrorNotice } from './GraphQLErrorNotice'; +type ValueWithoutServerSideErrors = { [key: string]: { kind: 'value'; value: any } }; + export function CreateItemDrawer({ listKey, onClose, @@ -35,10 +38,10 @@ export function CreateItemDrawer({ }` ); - const [valuesByFieldPath, setValuesByFieldPath] = useState(() => { - const value: Record = {}; + const [value, setValue] = useState(() => { + const value: ValueWithoutServerSideErrors = {}; Object.keys(list.fields).forEach(fieldPath => { - value[fieldPath] = list.fields[fieldPath].controller.defaultValue; + value[fieldPath] = { kind: 'value', value: list.fields[fieldPath].controller.defaultValue }; }); return value; }); @@ -46,8 +49,8 @@ export function CreateItemDrawer({ const invalidFields = useMemo(() => { const invalidFields = new Set(); - Object.keys(valuesByFieldPath).forEach(fieldPath => { - const val = valuesByFieldPath[fieldPath]; + Object.keys(value).forEach(fieldPath => { + const val = value[fieldPath].value; const validateFn = list.fields[fieldPath].controller.validate; if (validateFn) { @@ -58,35 +61,10 @@ export function CreateItemDrawer({ } }); return invalidFields; - }, [list, valuesByFieldPath]); + }, [list, value]); const [forceValidation, setForceValidation] = useState(false); - const fields = Object.keys(list.fields) - .filter(fieldPath => - createViewFieldModes.state === 'loaded' - ? createViewFieldModes.lists[listKey][fieldPath] !== 'hidden' - : false - ) - .map((fieldPath, index) => { - const field = list.fields[fieldPath]; - return ( - { - setValuesByFieldPath({ - ...valuesByFieldPath, - [fieldPath]: fieldValue, - }); - }} - autoFocus={index === 0} - /> - ); - }); - return ( = {}; Object.keys(list.fields).forEach(fieldPath => { const { controller } = list.fields[fieldPath]; - const serialized = controller.serialize(valuesByFieldPath[fieldPath]); + const serialized = controller.serialize(value[fieldPath].value); if (!isDeepEqual(serialized, controller.serialize(controller.defaultValue))) { Object.assign(data, serialized); } @@ -146,10 +124,20 @@ export function CreateItemDrawer({ {error && ( )} - - {fields} - {fields.length === 0 && 'There are no fields that you can read or edit'} - + + { + setValue(oldValues => getNewValue(oldValues) as ValueWithoutServerSideErrors); + }, [])} + /> + ); } diff --git a/packages/keystone/src/scripts/tests/build.test.ts b/packages/keystone/src/scripts/tests/build.test.ts index e1bf7d69311..064cc7fee13 100644 --- a/packages/keystone/src/scripts/tests/build.test.ts +++ b/packages/keystone/src/scripts/tests/build.test.ts @@ -65,12 +65,14 @@ test('build works with typescript without the user defining a babel config', asy expect(await fs.readFile(`${tmp}/node_modules/.keystone/types.js`, 'utf8')).toBe(''); expect( result - .all!.replace(/\d+(|\.\d+) k?B/g, 'size') - .replace(/chunks\/.*\.js/g, 'chunks/hash.js') - .replace( + .all!.replace( '\nwarn - No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache', '' ) + + // the exact formatting of the build size report can change when making unrelated changes + // because the code size can change so we don't include it in the snapshot + .replace(/info - Finalizing page optimization\.\.\.[^]+\n\n/, 'next build size report\n') ).toMatchInlineSnapshot(` "✨ Building Keystone ✨ Generating Admin UI code @@ -86,26 +88,7 @@ test('build works with typescript without the user defining a babel config', asy info - Generating static pages (2/6) info - Generating static pages (4/6) info - Generating static pages (6/6) - info - Finalizing page optimization... - - Page Size First Load JS - ┌ ○ / size size - ├ /_app size size - ├ ○ /404 size size - ├ λ /api/__keystone_api_build size size - ├ ○ /no-access size size - ├ ○ /todos size size - └ ○ /todos/[id] size size - + First Load JS shared by all size - ├ chunks/hash.js size - ├ chunks/hash.js size - ├ chunks/hash.js size - ├ chunks/hash.js size - ├ chunks/hash.js size - ├ chunks/hash.js size - ├ chunks/hash.js size - └ chunks/hash.js size - + next build size report λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)