reactjs - How to solve closures issue when using React functional component with React.memo?
问题描述
I am using React functional components alongside React.memo, but I am experiencing an issue, which in my opinion derives from JavaScript closures. (I am also using Immer for immutability, but I believe it does not affect the situation.)
Below is a simplified version of the situation:
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)}
/>
));
};
I am using React.memo to avoid unnecessary rerenders of the ChildComponents whose value didn't change, but this way the ChildComponent is storing an old version of the onChange function, which (in my understanding due to JavaScript closures) references an old version of the data.
The result of this is that when I change initially the value of the first ChildComponent and then the value of another ChildComponent, the value of the first ChildComponent is restored to the initial value.
A reproduction of the situation can be found in this sandbox.
After searching the web, a solution that I found to this issue is to use a ref inside the onChange function, to get the up-to-date data, like this:
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)}
/>
));
};
A sandbox with this solution can be found here.
This solves the issue. But I wanted to ask, is this a correct solution? Or am I using React and React Hooks the wrong way?
解决方案
Although using a reference is a valid solution, you should use a functional setState
.
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
const onDataChange = id => event => {
event.persist();
setData(data =>
produce(data, draft => {
const record = draft.find(entry => entry.id === id);
record.value = event.target.value;
})
);
};
推荐阅读
- regex - 为匹配特定模式的字符串添加前缀
- php - 我如何使用@ManyToMany Symfony4 持久化数据
- docker - 在创建 docker 容器时,我应该更喜欢在 Dockerfile 中定义安装,还是应该添加一个从 DockerFile 调用的脚本?
- css - React CSS 转换组 - 使用 post css 和 CX 时如何设置 className?
- r - Shiny:ggplot 的背景图片
- css - doxygen:参数/模板参数名称的垂直对齐
- reactjs - React-bootstrap (1.0.0-beta16) Carousel 在 Internet Explorer (IE) 中不起作用
- javascript - 通过js中的按钮触发Ctrl S
- python - 如何根据特定文件夹中的文件创建文件夹
- testing - 如何测试依赖项,通过 addDependency 添加?