-
Notifications
You must be signed in to change notification settings - Fork 643
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve performance of text inputs
This makes inputs only commit changes when they loose focus, instead of on every key press. This eliminates the janky feeling when typing in huge records while on a relatively slow device.
- Loading branch information
1 parent
f187eac
commit 82f6db4
Showing
8 changed files
with
222 additions
and
208 deletions.
There are no files selected for viewing
122 changes: 58 additions & 64 deletions
122
src/frontend/components/property-type/default-type/edit.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,75 @@ | ||
import React, { ReactNode } from 'react' | ||
/* eslint-disable @typescript-eslint/explicit-function-return-type */ | ||
import React, { FC, useState, memo, useEffect } from 'react' | ||
import Select from 'react-select' | ||
import { withTheme, DefaultTheme } from 'styled-components' | ||
import { Input, FormMessage, FormGroup, Label, selectStyles } from '@admin-bro/design-system' | ||
|
||
import { EditPropertyProps } from '../base-property-props' | ||
import { recordPropertyIsEqual } from '../record-property-is-equal' | ||
import usePrevious from '../../../utils/usePrevious' | ||
|
||
type CombinedProps = EditPropertyProps & {theme: DefaultTheme} | ||
|
||
class Edit extends React.Component<CombinedProps> { | ||
constructor(props) { | ||
super(props) | ||
this.handleInputChange = this.handleInputChange.bind(this) | ||
this.handleSelectChange = this.handleSelectChange.bind(this) | ||
} | ||
|
||
shouldComponentUpdate(prevProps: CombinedProps): boolean { | ||
return !recordPropertyIsEqual(prevProps, this.props) | ||
} | ||
const Edit: FC<CombinedProps> = (props) => { | ||
const { property, record } = props | ||
const error = record.errors?.[property.name] | ||
return ( | ||
<FormGroup error={Boolean(error)}> | ||
<Label | ||
htmlFor={property.name} | ||
required={property.isRequired} | ||
> | ||
{property.label} | ||
</Label> | ||
{property.availableValues ? <SelectEdit {...props} /> : <TextEdit {...props} />} | ||
<FormMessage>{error && error.message}</FormMessage> | ||
</FormGroup> | ||
) | ||
} | ||
|
||
handleInputChange(event): void { | ||
const { onChange, property } = this.props | ||
onChange(property.name, event.target.value) | ||
const SelectEdit: FC<CombinedProps> = (props) => { | ||
const { theme, record, property, onChange } = props | ||
if (!property.availableValues) { | ||
return null | ||
} | ||
const propValue = record.params?.[property.name] ?? '' | ||
const styles = selectStyles(theme) | ||
const selected = property.availableValues.find(av => av.value === propValue) | ||
return ( | ||
<Select | ||
isClearable | ||
styles={styles} | ||
value={selected} | ||
options={property.availableValues} | ||
onChange={s => onChange(property.name, s?.value ?? '')} | ||
isDisabled={property.isDisabled} | ||
/> | ||
) | ||
} | ||
|
||
handleSelectChange(selected): void { | ||
const { onChange, property } = this.props | ||
const value = selected ? selected.value : '' | ||
onChange(property.name, value) | ||
} | ||
const TextEdit: FC<CombinedProps> = (props) => { | ||
const { property, record, onChange } = props | ||
const propValue = record.params?.[property.name] ?? '' | ||
const [value, setValue] = useState(propValue) | ||
|
||
renderInput(): ReactNode { | ||
const { property, record, theme } = this.props | ||
const value = (record.params && typeof record.params[property.name] !== 'undefined') | ||
? record.params[property.name] | ||
: '' | ||
if (property.availableValues) { | ||
const styles = selectStyles(theme) | ||
const selected = property.availableValues.find(av => av.value === value) | ||
return ( | ||
<Select | ||
isClearable | ||
styles={styles} | ||
value={selected} | ||
options={property.availableValues} | ||
onChange={this.handleSelectChange} | ||
isDisabled={property.isDisabled} | ||
/> | ||
) | ||
const previous = usePrevious(propValue) | ||
useEffect(() => { | ||
// this means props updated | ||
if (propValue !== previous) { | ||
This comment has been minimized.
Sorry, something went wrong. |
||
setValue(propValue) | ||
} | ||
return ( | ||
<Input | ||
id={property.name} | ||
name={property.name} | ||
onChange={this.handleInputChange} | ||
value={value} | ||
disabled={property.isDisabled} | ||
/> | ||
) | ||
} | ||
}, []) | ||
|
||
render(): ReactNode { | ||
const { property, record } = this.props | ||
const error = record.errors && record.errors[property.name] | ||
return ( | ||
<FormGroup error={!!error}> | ||
<Label | ||
htmlFor={property.name} | ||
required={property.isRequired} | ||
> | ||
{property.label} | ||
</Label> | ||
{this.renderInput()} | ||
<FormMessage>{error && error.message}</FormMessage> | ||
</FormGroup> | ||
) | ||
} | ||
return ( | ||
<Input | ||
id={property.name} | ||
name={property.name} | ||
onChange={e => setValue(e.target.value)} | ||
onBlur={() => onChange(property.name, value)} | ||
value={value} | ||
disabled={property.isDisabled} | ||
/> | ||
) | ||
} | ||
|
||
export default withTheme(Edit) | ||
export default withTheme(memo(Edit, recordPropertyIsEqual)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
isnt React.memo handing this by default?