Skip to content

Commit

Permalink
Improve performance of create item modal with many fields (#6390)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Aug 24, 2021
1 parent c76bfc0 commit 2e3f366
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 61 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-turtles-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': patch
---

Improved performance of create item modal with many fields
64 changes: 26 additions & 38 deletions packages/keystone/src/admin-ui/components/CreateItemDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
/* @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';

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,
Expand All @@ -35,19 +38,19 @@ export function CreateItemDrawer({
}`
);

const [valuesByFieldPath, setValuesByFieldPath] = useState(() => {
const value: Record<string, unknown> = {};
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;
});

const invalidFields = useMemo(() => {
const invalidFields = new Set<string>();

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) {
Expand All @@ -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 (
<field.views.Field
key={fieldPath}
field={field.controller}
value={valuesByFieldPath[fieldPath]}
forceValidation={forceValidation && invalidFields.has(fieldPath)}
onChange={fieldValue => {
setValuesByFieldPath({
...valuesByFieldPath,
[fieldPath]: fieldValue,
});
}}
autoFocus={index === 0}
/>
);
});

return (
<Drawer
title={`Create ${list.singular}`}
Expand All @@ -103,7 +81,7 @@ export function CreateItemDrawer({
const data: Record<string, any> = {};
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);
}
Expand Down Expand Up @@ -146,10 +124,20 @@ export function CreateItemDrawer({
{error && (
<GraphQLErrorNotice networkError={error?.networkError} errors={error?.graphQLErrors} />
)}
<Stack gap="xlarge" paddingY="xlarge">
{fields}
{fields.length === 0 && 'There are no fields that you can read or edit'}
</Stack>
<Box paddingY="xlarge">
<Fields
fields={list.fields}
fieldModes={
createViewFieldModes.state === 'loaded' ? createViewFieldModes.lists[list.key] : null
}
forceValidation={forceValidation}
invalidFields={invalidFields}
value={value}
onChange={useCallback(getNewValue => {
setValue(oldValues => getNewValue(oldValues) as ValueWithoutServerSideErrors);
}, [])}
/>
</Box>
</Drawer>
);
}
29 changes: 6 additions & 23 deletions packages/keystone/src/scripts/tests/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit 2e3f366

Please sign in to comment.