Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memoize input and meta #640

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 116 additions & 94 deletions src/useField.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,108 +121,130 @@ function useField<FormValues: FormValuesShape>(
]
)

const handlers = {
onBlur: React.useCallback(
(event: ?SyntheticFocusEvent<*>) => {
state.blur()
const onBlur = React.useCallback(
(event: ?SyntheticFocusEvent<*>) => {
state.blur()
if (formatOnBlur) {
/**
* Here we must fetch the value directly from Final Form because we cannot
* trust that our `state` closure has the most recent value. This is a problem
* if-and-only-if the library consumer has called `onChange()` immediately
* before calling `onBlur()`, but before the field has had a chance to receive
* the value update from Final Form.
*/
const fieldState: any = form.getFieldState(state.name)
state.change(format(fieldState.value, state.name))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[state.name, format, formatOnBlur]
)
const onChange = React.useCallback(
(event: SyntheticInputEvent<*> | any) => {
// istanbul ignore next
if (process.env.NODE_ENV !== 'production' && event && event.target) {
const targetType = event.target.type
const unknown =
~['checkbox', 'radio', 'select-multiple'].indexOf(targetType) &&
!type

const value: any =
targetType === 'select-multiple' ? state.value : _value

if (unknown) {
console.error(
`You must pass \`type="${
targetType === 'select-multiple' ? 'select' : targetType
}"\` prop to your Field(${name}) component.\n` +
`Without it we don't know how to unpack your \`value\` prop - ${
Array.isArray(value) ? `[${value}]` : `"${value}"`
}.`
)
}
}

const value: any =
event && event.target
? getValue(event, state.value, _value, isReactNative)
: event
state.change(parse(value, name))
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[_value, name, parse, state.change, state.value, type]
)
const onFocus = React.useCallback((event: ?SyntheticFocusEvent<*>) => {
state.focus()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const meta = React.useMemo(() => {
const lazyObj = {}
addLazyFieldMetaState(lazyObj, state)
return lazyObj
}, [state])

const input = React.useMemo(() => {
const memoizedInput: FieldInputProps = {
name,
get value() {
let value = state.value
if (formatOnBlur) {
/**
* Here we must fetch the value directly from Final Form because we cannot
* trust that our `state` closure has the most recent value. This is a problem
* if-and-only-if the library consumer has called `onChange()` immediately
* before calling `onBlur()`, but before the field has had a chance to receive
* the value update from Final Form.
*/
const fieldState: any = form.getFieldState(state.name)
state.change(format(fieldState.value, state.name))
if (component === 'input') {
value = defaultFormat(value, name)
}
} else {
value = format(value, name)
}
if (value === null && !allowNull) {
value = ''
}
if (type === 'checkbox' || type === 'radio') {
return _value
} else if (component === 'select' && multiple) {
return value || []
}
return value
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[state.name, format, formatOnBlur]
),
onChange: React.useCallback(
(event: SyntheticInputEvent<*> | any) => {
// istanbul ignore next
if (process.env.NODE_ENV !== 'production' && event && event.target) {
const targetType = event.target.type
const unknown =
~['checkbox', 'radio', 'select-multiple'].indexOf(targetType) &&
!type

const value: any =
targetType === 'select-multiple' ? state.value : _value

if (unknown) {
console.error(
`You must pass \`type="${
targetType === 'select-multiple' ? 'select' : targetType
}"\` prop to your Field(${name}) component.\n` +
`Without it we don't know how to unpack your \`value\` prop - ${
Array.isArray(value) ? `[${value}]` : `"${value}"`
}.`
get checked() {
if (type === 'checkbox') {
if (_value === undefined) {
return !!state.value
} else {
return !!(
Array.isArray(state.value) && ~state.value.indexOf(_value)
)
}
} else if (type === 'radio') {
return state.value === _value
}

const value: any =
event && event.target
? getValue(event, state.value, _value, isReactNative)
: event
state.change(parse(value, name))
return undefined
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[_value, name, parse, state.change, state.value, type]
),
onFocus: React.useCallback((event: ?SyntheticFocusEvent<*>) => {
state.focus()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}

const meta = {}
addLazyFieldMetaState(meta, state)
const input: FieldInputProps = {
onChange,
onBlur,
onFocus
}

if (multiple) {
memoizedInput.multiple = multiple
}
if (type !== undefined) {
memoizedInput.type = type
}
return memoizedInput
}, [
name,
get value() {
let value = state.value
if (formatOnBlur) {
if (component === 'input') {
value = defaultFormat(value, name)
}
} else {
value = format(value, name)
}
if (value === null && !allowNull) {
value = ''
}
if (type === 'checkbox' || type === 'radio') {
return _value
} else if (component === 'select' && multiple) {
return value || []
}
return value
},
get checked() {
if (type === 'checkbox') {
if (_value === undefined) {
return !!state.value
} else {
return !!(Array.isArray(state.value) && ~state.value.indexOf(_value))
}
} else if (type === 'radio') {
return state.value === _value
}
return undefined
},
...handlers
}

if (multiple) {
input.multiple = multiple
}
if (type !== undefined) {
input.type = type
}
type,
multiple,
component,
allowNull,
format,
formatOnBlur,
_value,
state.value,
onChange,
onBlur,
onFocus
])

const renderProps: FieldRenderProps = { input, meta } // assign to force Flow check
return renderProps
Expand Down