反应:将行为与useEffect和/或useState挂钩混淆

时间:2020-06-21 05:29:51

标签: javascript reactjs

在这里反应菜鸟。我正在尝试制作一个简单的练习应用程序来帮助我进行React学习,并且出现了很多奇怪的行为。这是一个Todo应用,具有多个待办事项列表供您选择。我要执行的操作是待办事项列表的列表,您可以在其中选择一个和待办事项(例如wunderlist / msft待办事项)。选择一个不同的列表,它显示待办事项,等等。这时,它使用静态json,其中每个项目都有一个子数组。

useEffect-我正在尝试使用它来加载数据。它一直抱怨缺少依赖项。当我添加他们时,它也会抱怨。它抱怨我是否使用空数组作为第二个参数,并且似乎多次触发。

useState-我正在使用它来存储数据。它被初始化为一个空数组。但是render在useEffect之前触发,这是因为我的数据是未定义的,所以我的第二个列表从不渲染。

我在代码中有几个console.log,它们都多次触发。

我敢肯定这只是菜鸟的错误,但是我现在很困惑。这是我的代码:

data / Todo.js

const TodoData = [
  {
    Id: 1,
    Title: "Groceries",
    TodoList: [
      {
        Id: 1,
        Title: "Apples"
      },
      {
        Id: 2,
        Title: "Oranges"
      },
      {
        Id: 3,
        Title: "Bananas"
      }
    ]
  },
  {
    Id: 2,
    Title: "Daily Tasks",
    TodoList: [
      {
        Id: 11,
        Title: "Clean Kitchen"
      },
      {
        Id: 12,
        Title: "Feed Pets"
      },
      {
        Id: 13,
        Title: "Do Stuff"
      }
    ]
  },
  {
    Id: 3,
    Title: "Hardware Store",
    TodoList: []
  },
  {
    Id: 4,
    Title: "Costco",
    TodoList: [
      {
        Id: 21,
        Title: "Diapers"
      },
      {
        Id: 22,
        Title: "Cat Food"
      },
      {
        Id: 23,
        Title: "Apples"
      },
      {
        Id: 24,
        Title: "Bananas"
      }
    ]
  },
  {
    Id: 5,
    Title: "Work",
    TodoList: [
      {
        Id: 34,
        Title: "TPS Reports"
      }
    ]
  }
];

export default TodoData;

App.Js

import React, { useEffect, useState } from "react";
import TodoData from "./data/Todo.js";

function App() {
  const [todoData, setTodoData] = useState([]);
  const [todoDetails, setTodoDetails] = useState([]);

  useEffect(() => {
    if (todoData.length === 0) {
      getTodoData();
    }
  }, [todoData]);

  const getTodoData = () => {
    setTodoData(TodoData);
    console.log("getting todo data");
    getTodoDetails(1);
  };

  const getTodoDetails = id => {
    const result = TodoData.filter(x => x.Id === id);
    console.log(result[0]);
    setTodoDetails(result[0]);
  };

  const handleClick = e => {
    const selectedId = Number(e.target.getAttribute("data-id"));
    getTodoDetails(selectedId);
  };

  return (
    <div className="App">
      <div className="container-fluid">
        <div className="row">
          <div className="list-group col-md-4 offset-md-1">
            {todoData.map(todos => (
              <button
                key={todos.Id}
                data-id={todos.Id}
                className="btn list-group-item d-flex justify-content-between align-items-center"
                onClick={handleClick}
              >
                {todos.Title}
                <span className="badge badge-primary badge-pill">
                  {todos.TodoList.length}
                </span>
              </button>
            ))}
          </div>
          <div className="col-md-6">
            <ul className="list-group">
              <h2>{todoDetails.Title} List</h2>

              {console.log(todoDetails.TodoList)}

              {/* this fails miserably */}
              {/* {todoDetails.TodoList.map(details => (
                <li className="list-group-item" key={details.Id}>
                  {details.Title}
                </li>
              ))} */}

              <li className="list-group-item">Cras justo odio</li>
              <li className="list-group-item">Dapibus ac facilisis in</li>
              <li className="list-group-item">Morbi leo risus</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
演示:https://codesandbox.io/s/interesting-dan-rdoyh?fontsize=14&hidenavigation=1&theme=dark

4 个答案:

答案 0 :(得分:4)

Q: useEffect-我正在尝试使用它来加载数据。它一直抱怨缺少依赖项

A:,您可以通过eslint (react-hooks/exhaustive-deps)忽略它,没有什么可担心的


Q: useState-我正在使用它来存储数据。它被初始化为一个空数组。但是render在useEffect之前触发,这是因为我的数据是未定义的,所以我的第二个列表从不渲染。

A:请阅读代码中的注释,希望能消除您的疑问

// you are intializing `todoDetails` with array, when there will be object
// hence the error while mapping inside the html/jsx
// const [todoDetails, setTodoDetails] = useState([]);

// init with `null`, why?
const [todoDetails, setTodoDetails] = useState(null);

// so that you can do something like this
{
  todoDetails && (   // <---------- HERE
    <ul className="list-group">
      <h2>{todoDetails.Title} List</h2>

      {console.log(todoDetails.TodoList)}

      {/* this fails miserably */}
      {todoDetails.TodoList.map(details => (
        <li className="list-group-item" key={details.Id}>
          {details.Title}
        </li>
      ))}
    </ul>
  )
}

Q:现在您将获得多个console.log

A:原因是<React.StrictMode>

React可以在提交之前多次调用渲染阶段生命周期,也可以根本不提交而调用它们(由于错误或更高优先级的中断)。

我已在演示中将其从index.js中删除,因此您可以看到差异


工作演示:

Edit angry-oskar-37rq9

答案 1 :(得分:2)

您注释并说失败的代码是由于todoDetails.TodoList在您首次渲染组件时未定义。为避免这种情况,请用一个项目初始化todoDetails或在调用todoDetails.TodoList之前检查是否定义了.map

{todoDetails.TodoList ? todoDetails.TodoList.map(details => (
  <li className="list-group-item" key={details.Id}>
    {details.Title}
  </li>
)) : null}

const [todoDetails, setTodoDetails] = useState(TodoData[0]);

不必太担心eslint (react-hooks/exhaustive-deps)错误。您需要了解useEffect的第二个参数的含义。在您的代码中,您将todoData传递给了useEffect

  useEffect(() => {
    if (todoData.length === 0) {
      getTodoData();
    }
  }, [todoData]);

这意味着,每当todoData发生变化时,useEffect就会像componentDidUpdate一样运行。如果仅传递一个空数组,则意味着您的useEffect没有依赖项,并且将仅执行一次,其行为类似于componentDidMount。所以,您在这里做的很好。

除此之外,以下是一些我可以为您提供的代码的提示:

关于您的handleClick函数,您可以删除它,并简单地调用getTodoDetails(id)传递ID,而不是将其添加到data-id并在以后通过以下方式获取它:

<button
  key={todos.Id}
  className="btn list-group-item d-flex justify-content-between align-items-center"
  onClick={() => getTodoDetails(todos.Id)} // <---- change is in here
>
  {todos.Title}
  <span className="badge badge-primary badge-pill">
    {todos.TodoList.length}
  </span>
</button>

在您的getTodoDetails上,您可以将filter更改为find,因为您不需要遍历整个数组来查找一项,因此价格更高。另外,这样,您就不需要访问数组的第一项。

const getTodoDetails = id => {
  const result = TodoData.find(x => x.Id === id);
  console.log(result);
  setTodoDetails(result);
};

您将todoDetails用作object,并将其初始化为array。如果您的todoDetails总是只有一个待办事项,请将其初始化为一个对象,这样您就可以防止从数组访问todoDetails.TitleuseEffect将始终在渲染后执行。

const [todoDetails, setTodoDetails] = useState({});

答案 2 :(得分:1)

您必须将getTodoData的逻辑放入useEffect钩子中

useEffect(() => {
    const getTodoData = () => {
      setTodoData(TodoData);
      console.log("getting todo data");
      getTodoDetails(1);
    };

    if (todoData.length === 0) {
      getTodoData();
    }
  }, [todoData.length]);

渲染中

{
  // check todoDetails has TodoList
  todoDetails.TodoList &&
    todoDetails.TodoList.map((details) => (
      <li className="list-group-item" key={details.Id}>
        {details.Title}
      </li>
    ));
}

这是更新后的demo

答案 3 :(得分:1)

useEffect挂钩有时可能很奇怪。要解决此问题,您只需像下面这样直接传递getTodoData函数:

 const getTodoData = () => {
  setTodoData(TodoData);
  console.log("getting todo data");
  getTodoDetails(1);
};

useEffect(getTodoData, []);

在将函数getTodoData传递到useEffect之前定义App.js函数很重要,否则会出错。

我对您的“惨败”的代码未加注释,并且能够使所有内容正常运行。

这是您的固定import React, { useEffect, useState } from "react"; import TodoData from "./data/Todo.js"; function App() { const [todoData, setTodoData] = useState([]); const [todoDetails, setTodoDetails] = useState([]); const getTodoData = () => { setTodoData(TodoData); console.log("getting todo data"); getTodoDetails(1); }; useEffect(getTodoData, []); const getTodoDetails = id => { const result = TodoData.filter(x => x.Id === id); console.log(result[0]); setTodoDetails(result[0]); }; const handleClick = e => { const selectedId = Number(e.target.getAttribute("data-id")); getTodoDetails(selectedId); }; return ( <div className="App"> <div className="container-fluid"> <div className="row"> <div className="list-group col-md-4 offset-md-1"> {todoData.map(todos => ( <button key={todos.Id} data-id={todos.Id} className="btn list-group-item d-flex justify-content-between align-items-center" onClick={handleClick} > {todos.Title} <span className="badge badge-primary badge-pill"> {todos.TodoList.length} </span> </button> ))} </div> <div className="col-md-6"> <ul className="list-group"> <h2>{todoDetails.Title} List</h2> {console.log(todoDetails.TodoList)} {todoDetails.TodoList.map(details => ( <li className="list-group-item" key={details.Id}> {details.Title} </li> ))} </ul> </div> </div> </div> </div> ); } export default App; 固定文件的全部:

useEffect

This Stackoverflow post与您的相似,可能会帮助您了解将来在使用 position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, alignItems: 'center', justifyContent: 'center' 钩子时如何避免错误。我希望这会有所帮助!