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>