React JS - 在 child 单击时更改 parent 状态,地图不可迭代
React JS - Change parent state on child click, map not iterable
总的来说,我是 React 和开发人员的新手,但我正在努力弄清楚如何实现我想要做的事情。我觉得我好像错过了一些东西。
我的目标是拥有一个项目列表,单独单击的项目将切换其信息的可见性。
问题是我无法映射 parent 元素中的状态来显示每个 object。但是状态在一个数组中,所以我不明白为什么它不能迭代。当我将道具传递给没有状态的 child 只是一个 object 时,我没有遇到这个问题。
这是执行此操作的正确方法吗?我是否应该创建另一个数组来映射我的 object?我也有点困惑,因为一些来源创建了 class 并使用构造函数和渲染函数。这是已弃用还是我应该这样做?
Parent
import React from "react";
import { useState } from "react";
//Components
import Card from "./Card";
const CardStack = () => {
const [habits, setHabits] = [
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
];
const toggleCard = () => {
this.setHabits((habit) => {
habit.visible = !visible;
});
};
return (
<div className="card-stack">
{habits.map((habit) => (
<Card habit={habit} key={habit.id} onClick={toggleCard} />
))}
</div>
);
};
export default CardStack;
Child
import React from "react";
//Components
import Button from "./Button";
const Cards = ({ habit, onClick }) => {
return (
<div className="card" key={habit.id} onClick={onClick}>
<h4 className="title" merit={habit.merit}>
{habit.title}
<div className="btn-group">
<Button className="button" />
<span className="count">{habit.count}</span>
<Button className="button" />
</div>
{habit.visible ? (
<div className="content">
<p>visible</p>
</div>
) : null}
</h4>
</div>
);
};
export default Cards;
您好像忘记调用 useState 了?
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
]);
如你所愿:
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
];
habits.map()
-->Uncaught TypeError: habits.map is not a function```
你的代码有很多问题。
@talfreds 在他们的回答中指出了第一个——你需要调用 useState()
来初始化状态变量及其对应的 setter.
const CardStack = () => {
const [habits, setHabits] = useState([
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
...]);
只需执行此操作即可让您的组件呈现。
但是一旦您单击该按钮,您当前的 toggle
处理程序将使用布尔值覆盖存储在 habits
中的数组。
要解决此问题,您需要了解传递给 setState
的回调会传递相关状态变量的当前值供您使用,并且状态将设置为您指定的值return 来自回调。当使用数组时,你需要避免直接改变这个传递的值,在这个例子中通过使用 map()
其中 return 是一个新数组,并通过克隆我们正在更改的 'habit' 对象使用 spread语法。
const toggleCard = (id) => { // pass the id of the 'habit' to toggle
setHabits((habits) => { // the current 'habits' array is passed to the callback
// return a new array and avoid mutating nested objects when updating it
return habits.map((habit) => habit.id === id ? { ...habit, visible: !habit.visible } : habit);
});
};
// usage
{habits.map((habit) => (
...
<button type="button" onClick={() => toggleCard(habit.id)}>Toggle</button>
...
)}
最后一个明显的问题是您使用 this
,这在使用基于 class 的组件时是必需的,但在功能组件中不是必需的,实际上根本不起作用在箭头函数的上下文中。
这是一个简短的示例片段,可以帮助您理解这些想法。
const { useEffect, useState } = React;
const App = () => {
const [ habits, setHabits ] = useState([ // call useState to initialize 'habits' state
{
id: 1,
merit: 'good',
title: 'Good Habit',
count: 4,
text: 'Words to be hidden',
visible: false,
},
{
id: 2,
merit: 'bad',
title: 'Bad Habit',
count: 1,
text: 'Words to be hidden',
visible: false,
},
{
id: 3,
merit: 'good',
title: 'Good Habit',
count: 6,
text: 'Words to be hidden',
visible: true,
},
]);
useEffect(() => {
console.log('This: ', this);
}, []);
const toggleCard = (id) => { // id passed from mapped buttons
setHabits((habits) => { // the current 'habits' array is passed to the callback
// return a new array and avoid mutating nested objects when updating it
return habits.map((habit) => habit.id === id ? { ...habit, visible: !habit.visible } : habit);
});
};
return (
<div className="card-stack">
{habits.map((habit) => (
<div key={habit.id} className="card">
<h3>{habit.title}</h3>
{habit.visible
? (<p>{habit.text}</p>)
: null}
<button type="button" onClick={() => toggleCard(habit.id)}>Toggle</button>
</div>
))}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
总的来说,我是 React 和开发人员的新手,但我正在努力弄清楚如何实现我想要做的事情。我觉得我好像错过了一些东西。
我的目标是拥有一个项目列表,单独单击的项目将切换其信息的可见性。
问题是我无法映射 parent 元素中的状态来显示每个 object。但是状态在一个数组中,所以我不明白为什么它不能迭代。当我将道具传递给没有状态的 child 只是一个 object 时,我没有遇到这个问题。
这是执行此操作的正确方法吗?我是否应该创建另一个数组来映射我的 object?我也有点困惑,因为一些来源创建了 class 并使用构造函数和渲染函数。这是已弃用还是我应该这样做?
Parent
import React from "react";
import { useState } from "react";
//Components
import Card from "./Card";
const CardStack = () => {
const [habits, setHabits] = [
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
];
const toggleCard = () => {
this.setHabits((habit) => {
habit.visible = !visible;
});
};
return (
<div className="card-stack">
{habits.map((habit) => (
<Card habit={habit} key={habit.id} onClick={toggleCard} />
))}
</div>
);
};
export default CardStack;
Child
import React from "react";
//Components
import Button from "./Button";
const Cards = ({ habit, onClick }) => {
return (
<div className="card" key={habit.id} onClick={onClick}>
<h4 className="title" merit={habit.merit}>
{habit.title}
<div className="btn-group">
<Button className="button" />
<span className="count">{habit.count}</span>
<Button className="button" />
</div>
{habit.visible ? (
<div className="content">
<p>visible</p>
</div>
) : null}
</h4>
</div>
);
};
export default Cards;
您好像忘记调用 useState 了?
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
]);
如你所愿:
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
{
id: 2,
merit: "bad",
title: "Bad Habit",
count: 1,
text: "Words to be hidden",
visible: false,
},
{
id: 3,
merit: "good",
title: "Good Habit",
count: 6,
text: "Words to be hidden",
visible: true,
},
];
habits.map()
-->Uncaught TypeError: habits.map is not a function```
你的代码有很多问题。
@talfreds 在他们的回答中指出了第一个——你需要调用 useState()
来初始化状态变量及其对应的 setter.
const CardStack = () => {
const [habits, setHabits] = useState([
{
id: 1,
merit: "good",
title: "Good Habit",
count: 4,
text: "Words to be hidden",
visible: false,
},
...]);
只需执行此操作即可让您的组件呈现。
但是一旦您单击该按钮,您当前的 toggle
处理程序将使用布尔值覆盖存储在 habits
中的数组。
要解决此问题,您需要了解传递给 setState
的回调会传递相关状态变量的当前值供您使用,并且状态将设置为您指定的值return 来自回调。当使用数组时,你需要避免直接改变这个传递的值,在这个例子中通过使用 map()
其中 return 是一个新数组,并通过克隆我们正在更改的 'habit' 对象使用 spread语法。
const toggleCard = (id) => { // pass the id of the 'habit' to toggle
setHabits((habits) => { // the current 'habits' array is passed to the callback
// return a new array and avoid mutating nested objects when updating it
return habits.map((habit) => habit.id === id ? { ...habit, visible: !habit.visible } : habit);
});
};
// usage
{habits.map((habit) => (
...
<button type="button" onClick={() => toggleCard(habit.id)}>Toggle</button>
...
)}
最后一个明显的问题是您使用 this
,这在使用基于 class 的组件时是必需的,但在功能组件中不是必需的,实际上根本不起作用在箭头函数的上下文中。
这是一个简短的示例片段,可以帮助您理解这些想法。
const { useEffect, useState } = React;
const App = () => {
const [ habits, setHabits ] = useState([ // call useState to initialize 'habits' state
{
id: 1,
merit: 'good',
title: 'Good Habit',
count: 4,
text: 'Words to be hidden',
visible: false,
},
{
id: 2,
merit: 'bad',
title: 'Bad Habit',
count: 1,
text: 'Words to be hidden',
visible: false,
},
{
id: 3,
merit: 'good',
title: 'Good Habit',
count: 6,
text: 'Words to be hidden',
visible: true,
},
]);
useEffect(() => {
console.log('This: ', this);
}, []);
const toggleCard = (id) => { // id passed from mapped buttons
setHabits((habits) => { // the current 'habits' array is passed to the callback
// return a new array and avoid mutating nested objects when updating it
return habits.map((habit) => habit.id === id ? { ...habit, visible: !habit.visible } : habit);
});
};
return (
<div className="card-stack">
{habits.map((habit) => (
<div key={habit.id} className="card">
<h3>{habit.title}</h3>
{habit.visible
? (<p>{habit.text}</p>)
: null}
<button type="button" onClick={() => toggleCard(habit.id)}>Toggle</button>
</div>
))}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>