简体   繁体   中英

React.useCallback() for chained anonymous functions

Often, in order to pass an onChange callback function to a component you do something like this:

const handleChange = (e) => {
  console.log(e.target.value)
}

return (
  <Component onChange={handleChange} />
)

If you don't want the Component to render every time the main component does, you must wrap it in a React.useCallback() hook as follow:

const handleChange = React.useCallback((e) => {
  console.log(e.target.value)
}, [])

return (
  <Component onChange={handleChange} />
)

In a case where this input is generated in a for loop based on a list, you want the callback to know about the source of the change. The most common way of passing the information is by chaining lambdas as follow:

const fields = ['field1', 'field2', 'field3']

const handleChange = (fieldName) => (e) => {
  console.log(`${fieldName}: `, e.target.value)
}

return (
  <>
    {fields.map((x) => <Component onChange={handleChange(x)} />}
  </>
)

But in this case, it is kind of impossible to memoize the outer and inner lambdas while keeping the entire context in the function with a React.useCallback() hook.

In most cases, I have control on the component and change the callback to include the wanted data, but often it happens that the component is from a library (ig Material-UI) and, therefore, cannot be modified.

In what way could this callback be entirely memoized with React Hooks?

You can generate event handlers for each elements, and memoized the array of event handlers. When create the elements, take the relevant event handler by it's index:

 const { memo, useMemo } = React const Demo = ({ fields }) => { const handlers = useMemo(() => fields.map(fieldName => e => { console.log(`${fieldName}: `, e.target.value) }) , [fields]) return ( <div> { fields.map( (fieldName, i) => ( <input key={fieldName} onChange={handlers[i]} /> )) } </div> ) } const fields = ['field1', 'field2', 'field3'] ReactDOM.render( <Demo fields={fields} />, root )
 <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <div id="root"></div>

How i would handle this is by creating a state and passing it as a dependency to the useCallback hook like:

const [fields, setFields] = useState({'field1': "", 'field2': "", 'field3': ""});

const handleChange = React.useCallback((e) => {
  console.log(`${fieldName}: `, e.target.value);
  if(e.target.id === "field1") {
    let newFields = {...fields};
    fields.field1 = e.target.value;
    setFields(newFields);
  }
}, [fields])

return (
  <>
    {fields.map((x) => <Component onChange={handleChange(x)} />}
  </>
)

Hope this works for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM