Я использую функциональные компоненты React вместе с React.memo, но у меня возникла проблема, которая, на мой взгляд, связана с закрытием JavaScript. (Я также использую Immer для неизменности, но считаю, что это не влияет на ситуацию.)
Ниже представлена упрощенная версия ситуации:
import React, { useState } from 'react';
import produce from "immer";
const initialData = [
{id: 1, value: 0},
{id: 2, value: 0},
{id: 3, value: 0}
]
const ChildComponent = memo(
({ value, onChange }) => (
<p>
<input value={value} onChange={onChange} type="number" />
</p>
),
(prevProps, nextProps) => prevProps.value === nextProps.value
);
const ParentComponent = props => {
const [data, setData] = useState(initialData);
const onDataChange = id => event => {
setData(
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
return data.map(record => (
<ChildComponent
key={record.id}
value={record.value}
onChange={onDataChange(record.id)}
/>
));
};
Я использую React.memo, чтобы избежать ненужных повторных отображений ChildComponents, значение которых не изменилось, но таким образом ChildComponent хранит старую версию функции onChange, которая (в моем понимании из-за закрытий JavaScript) ссылается на старую версию данные.
В результате, когда я сначала изменяю значение первого ChildComponent, а затем значение другого ChildComponent, значение первого ChildComponent восстанавливается до начального значения.
Воспроизведение ситуации можно найти в этой песочнице.
После поиска в Интернете я нашел решение этой проблемы - использовать ссылку внутри функции onChange, чтобы получить актуальные данные, например:
const ParentComponent = props => {
const [data, setData] = useState(initialData);
const dataRef = useRef();
dataRef.current = data;
const onDataChange = id => event => {
setData(
produce(dataRef.current, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
return data.map(record => (
<ChildComponent
key={record.id}
value={record.value}
onChange={onDataChange(record.id)}
/>
));
};
Песочницу с этим решением можно найти здесь.
Это решает проблему. Но я хотел спросить: это правильное решение? Или я неправильно использую React и React Hooks?