我正在使用 Formik 和 MUI 开发 ToDo 应用程序。我从弹出框内的选择按钮切换为单选按钮,单击图标时会打开该弹出框。但是使用单选按钮,更改其中的值时不会触发渲染。我尝试了许多其他方法来处理它并强制更新渲染。没有任何效果,所以我将当前混乱的代码状态放入沙箱中,并希望任何人都可以帮助我。
https://codesandbox.io/p/sandbox/kfng7y
它应该更新状态并重新渲染组件以显示正确的状态,但它不会重新渲染并且不显示更新后的状态
好吧,我调试了几天才学到一些东西-
将 JSX 置于状态会创建“状态闭包”。他们将“记住”创建它们时存在的值,而不是渲染它们时存在的值。
TL;博士 任何需要对变化的值保持反应的组件都需要直接在 JSX 中呈现,而不是存储在状态中。
将数据存储在状态中,而不是组件中。
将状态用于不可变对象(字符串、数字、布尔值)或数据对象或 UI 状态标志(isOpen、activeTab 等)
不要对 JSX 元素、React 组件以及任何其他需要访问 props 或其他变化值的东西使用状态。
所以不要将 JSX 置于这样的状态:
const TodoForm: React.FC<ToDoFormProps> = ({ onSubmit }) => {
const [popoverContent, setPopoverContent] = useState<React.ReactNode>(null);
// DON'T: Storing JSX in state that needs access to changing values
const handleIconClick = (
event: React.MouseEvent<HTMLElement>,
content: React.ReactNode
) => {
setAnchorEl(event.currentTarget);
setPopoverContent(content);
};
您应该仅将 JSX 保留在返回语句中。切勿将其置于某种状态:
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const [activePopover, setActivePopover] = useState<"date" | "tag" | "priority" | null>(null);
// DO: Render components directly with current values
const handleIconClick = (
event: React.MouseEvent<HTMLElement>,
type: "date" | "tag" | "priority"
) => {
setAnchorEl(event.currentTarget);
setActivePopover(type);
};
const handlePopoverClose = () => {
setAnchorEl(null);
setActivePopover(null);
};
// Render popover content based on active type
const renderPopoverContent = (values: any, setFieldValue: any) => {
switch (activePopover) {
case "date":
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDateTimePicker
ampm={false}
label="Fälligkeitsdatum"
value={values.dueDate}
onChange={(newValue) => {
setFieldValue("dueDate", newValue);
}}
minDate={new Date()}
/>
</LocalizationProvider>
);
case "tag":
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<RadioGroup
name="tag"
value={values.tag}
onChange={(e) => {
setFieldValue("tag", e.target.value);
}}
>
{tags.map((tag) => (
<FormControlLabel
key={tag}
value={tag}
control={<Radio />}
label={tag}
sx={{
border: "1px solid #ccc",
borderRadius: 2,
p: 1,
m: 0.5,
}}
/>
))}
</RadioGroup>
</Box>
);
case "priority":
return (
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<RadioGroup
name="priority"
value={values.priority}
onChange={(e) => {
setFieldValue("priority", e.target.value);
}}
>
<FormControlLabel
value="low"
control={<Radio />}
label="Niedrig"
sx={{
border: "1px solid #ccc",
borderRadius: 2,
p: 1,
m: 0.5,
}}
/>
<FormControlLabel
value="medium"
control={<Radio />}
label="Mittel"
sx={{
border: "1px solid #ccc",
borderRadius: 2,
p: 1,
m: 0.5,
}}
/>
<FormControlLabel
value="high"
control={<Radio />}
label="Hoch"
sx={{
border: "1px solid #ccc",
borderRadius: 2,
p: 1,
m: 0.5,
}}
/>
</RadioGroup>
</Box>
);
default:
return null;
}
};
return (
<>
<Grid container spacing={1} alignItems="center">
<IconButton onClick={(e) => handleIconClick(e, "date")}>
<CalendarTodayIcon />
</IconButton>
<IconButton onClick={(e) => handleIconClick(e, "tag")}>
<TagIcon />
</IconButton>
<IconButton onClick={(e) => handleIconClick(e, "priority")}>
<PriorityHighIcon />
</IconButton>
</Grid>
<Popover
open={Boolean(anchorEl)}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
>
<Box sx={{ p: 2 }}>
{renderPopoverContent(values, setFieldValue)}
</Box>
</Popover>
</>
)