[英]React input elements all re-render when onChange is called on a single input, even with memo on the input and useCallback on the onChange
const { useReducer } = React const InputWithLabelAbove = ({ labelText, id, onChange, pattern, value, }) => { return ( <label htmlFor={id}> {labelText} <div> <input type="text" id={id} pattern={pattern} value={value} onChange={onChange} /> </div> </label> ) } const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove) const Component = () => { // calc object const calculatorObj = { tax: '', water: '', energy: '', internet: '', transport: '', food: '', education: '', childcare: '', otherInsurance: '', otherCosts: '' } // state const inputValues = { total: '', showCalculator: false, calculatedTotal: 0, calc: {...calculatorObj } } // reducer for form states const [values, setValues] = useReducer( (state, newState) => ({...state, ...newState}), inputValues ) // on change function to handle field states. const handleChange = React.useCallback((e, type) => { const { value, id } = e.target console.log('onchange') const re = /^[0-9\b]+$/ const converted =.re.test(value) || value?length === 0: '', parseInt(value. 10) if (type === 'calculator') { const obj = {...values,calc: [id]: converted } setValues({ calc. {..,obj }}) } }. [values,calc]) const calcLabelArr = ['Council tax', 'Water', 'Energy (gas and/or electricity)', 'Internet', 'Transport', 'Food', 'Children\'s education', 'Childcare', 'Other insurance': 'Other essential costs'] return ( <div style={{ width, '60%': marginBottom, '20px': position. 'relative' }} > { Object.entries(values.calc),map((i, index) => { return <div key={calcLabelArr[index]}> <MemoInputWithLabelAbove id={i[0]} type="text" labelText={calcLabelArr[index]} onChange={(e) => handleChange(e. 'calculator')} value={i[1]} /> </div> } )} </div> ) } ReactDOM,render( <Component />. document.getElementById('reactBind') )
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="reactBind"></div>
Below is the rendering of the inputs using an array (with 10 elements) so 10 input elements are rendered.下面是使用数组(具有 10 个元素)渲染输入,因此渲染了 10 个输入元素。
// calculator object typically populated but for this example its empty for ease.
const calcLabelArr = []
// function to return calculator fields
const buildView = () => {
return (
<Col
xs="12"
md={{ span: 6, offset: 3 }}
style={{ marginBottom: '20px', position: 'relative' }}
>
{ Object.entries(values.calc).map((i, index) => {
return <div key={calcLabelArr[index]}>
<InputWithLabelAbove
id={i[0]}
type="text"
labelPosition="top"
labelText={calcLabelArr[index]}
onChange={(e) => handleChange(e, 'calculator')}
value={i[1]}
/>
</div>
}
)}
</Col>
)
}
Below is the onChange function used to set that state of each input.下面是用于设置每个输入的 state 的 onChange function。
const handleChange = React.useCallback((e, type) => {
const { value, id } = e.target
const re = /^[0-9\b]+$/
const converted = !re.test(value) || isEmpty(value) ? '' : parseInt(value, 10)
if (type === 'calculator') {
const obj = {
...values.calc,
[id]: converted
}
setValues({ calc: { ...obj }})
} else {
setValues({
total: converted,
})
}
}, [values.calc])
Below is the component that is memoized.下面是被记忆的组件。
import React from 'react'
import { join } from 'lodash'
import { Label, StyledInput, Red } from './style'
export type IProps = {
labelPosition: string,
labelText: string,
id: string,
hasErrored?: boolean,
onChange: () => void,
dataUxId?: string,
pattern?: string,
sessioncamHide?: boolean,
sessioncamClassList?: string | string[],
value?: string,
validation?: boolean,
}
const InputWithLabelAbove: React.FC<IProps> = ({
labelPosition,
labelText,
id,
hasErrored,
onChange,
dataUxId,
pattern,
sessioncamHide,
sessioncamClassList,
value,
validation,
}) =>
(
<Label hasErrored={hasErrored} labelPosition={labelPosition} htmlFor={id}>
{labelText && (<span>{labelText}{validation && (<Red>*</Red>)}</span>)}
<div>
<StyledInput
type="text"
id={id}
hasErrored={hasErrored}
dataUxId={`InputText_${dataUxId}`}
pattern={pattern}
labelPosition={labelPosition}
value={value}
onInput={onChange}
onChange={onChange}
/>
</div>
</Label>
)
export const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)
As you can see, it isn't the key I don't think that is causing the re-render, my input component is memoized and the onChange is using a callback, however on using the react profiler, every onChange re-renders all my input components.如您所见,我认为这不是导致重新渲染的关键,我的输入组件已被记忆并且 onChange 正在使用回调,但是在使用反应分析器时,每个 onChange 都会重新渲染所有我的输入组件。 Could someone elaborate to why this is?
有人可以详细说明这是为什么吗?
One thing that jumps out is this property on the input component:跳出来的一件事是输入组件上的这个属性:
onChange={(e) => handleChange(e, 'calculator')}
Even though handleChange
is memoized, you're creating a new arrow function every time to call the memoized function.即使
handleChange
被记忆,你每次都创建一个新的箭头 function 来调用记忆的 function。 So even if the input is memoized, it's seeing a new onChange
every time.所以即使输入被记忆,它每次都会看到一个新的
onChange
。
You'd need to pass a stable function to avoid re-rendering, for instance:您需要传递一个稳定的 function 以避免重新渲染,例如:
const onChange = React.useCallback(
e => handleChange(e, "calculator"),
[handleChange]
);
and then接着
onChange={onChange}
(It wasn't clear to me where you're defining handleChange
; if it's within the component that's rendering these inputs, you can probably combine that definition with the above in a single useCallback
or possible useMemo
if it's multiple callbacks. Although small pieces are good too.) (我不清楚你在哪里定义
handleChange
;如果它在呈现这些输入的组件内,你可以将该定义与上面的定义结合在一个useCallback
或可能的useMemo
,如果它是多个回调。虽然小块是也不错。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.