Состояние не обновляется должным образом при использовании эффектов React - PullRequest
1 голос
/ 08 мая 2020

Я работаю над небольшим приложением todo в качестве упражнения с использованием React. У меня есть макет службы вроде этого:

export default class TodoService {

    constructor(todos) {
        this.todos = new Map();
        todos.forEach(todo => {
            this.todos.set(todo.id, todo);
        });
    }

    findAll() {
        return Array.from(this.todos.values());
    }

    saveTodo(todo) {
        this.todos[todo.id] = todo
    }

    completeTodo(todo) {
        this.todos.delete(todo.id)
    }
}

, и в моем приложении React у меня есть состояние, которое содержит задачи:

const [todos, setTodos] = useState([]);
const [flip, flipper] = useState(true);

const completeTodo = (todo) => {
    todoService.completeTodo(todo);
    flipper(!flip);
}

useEffect(() => {
    setTodos(todoService.findAll());
}, [flip])

completeTodo - это функция, в которую я передаю my Todo компонент, который будет использоваться, когда я хочу выполнить задачу следующим образом:

import React from "react";
const Todo = ({ todo, completeFn }) => {
    return (
        <form className="todo">
            <div className="form-check">
                <input
                    className="form-check-input"
                    type="checkbox"
                    value=""
                    name={todo.id}
                    id={todo.id}
                    onClick={() => {
                        console.log(`completing todo...`)
                        completeFn(todo)
                    }} />
                <label className="form-check-label" htmlFor={todo.id}>
                    {todo.description}
                </label>
            </div>
        </form>
    )
}
export default Todo

Итак, что происходит, когда пользователь нажимает флажок, completeFn вызывается с todo, он удаляется из служебного объекта, и состояние должно обновляться, но происходит самое странное.

Когда вызывается TodoService.completeTodo(), задача удаляется правильно, но когда вызывается findAll(), старая задача все еще там! Если я напишу содержимое в консоль, я увижу, что элемент удаляется, а затем каким-то образом телепортируется обратно, когда я звоню findAll. Почему это происходит? Я это из-за некоторого React magi c Я не понимаю?

Edit: Что еще более безумно, так это то, что если я изменю это, чтобы использовать эффекты только для начальной загрузки, как это :

const [todos, setTodos] = useState([]);

const completeTodo = (todo) => {
    todoService.completeTodo(todo);
    setTodos(todoService.findAll());
}

useEffect(() => {
    setTodos(todoService.findAll());
}, [])

Я получаю супер странный результат:

enter image description here

Кто-нибудь может мне это объяснить ?

Edit2: Это полностью воспроизводимый пример (без index.html с <div id="root"></div> в нем).

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const Todo = ({ todo, completeFn }) => {
    return (
        <div>
            <input
                type="checkbox"
                name={todo.id}
                id={todo.id}
                onClick={() => {
                    console.log(`completing todo...`)
                    completeFn(todo)
                }} />
            <label className="form-check-label" htmlFor={todo.id}>
                {todo.description}
            </label>
        </div>
    )
}

class TodoService {

    constructor(todos) {
        this.todos = new Map();
        todos.forEach(todo => {
            this.todos.set(todo.id, todo);
        });
    }

    findAll() {
        return Array.from(this.todos.values());
    }

    saveTodo(todo) {
        this.todos[todo.id] = todo
    }

    completeTodo(todo) {
        this.todos.delete(todo.id)
    }
}

const App = () => {

    let todoService = new TodoService([{
        id: 1,
        description: "Let's go home."
    }, {
        id: 2,
        description: "Take down the trash"
    }, {
        id: 3,
        description: "Play games"
    }]);

    const [todos, setTodos] = useState([]);
    const [flip, flipper] = useState(true);

    const completeTodo = (todo) => {
        todoService.completeTodo(todo);
        flipper(!flip);
    }

    useEffect(() => {
        setTodos(todoService.findAll());
    }, [flip])

    return (
        <div>
            {todos.map(todo => <Todo key={todo.id} todo={todo} completeFn={completeTodo} />)}
        </div>
    )
};
ReactDOM.render(<App />, document.getElementById("root"));

1 Ответ

1 голос
/ 08 мая 2020

В этом сценарии вам не нужно звонить useEffect. Вы поместили зависимость в useEffect, которая позволяет использовать ее для остановки бесконечного l oop. но здесь это не нужно. Вы на самом деле ничего не делаете fetch

Вы можете обновить свой код, чтобы он был таким.

import React, { useState, useCallback, useEffect } from "react";

const Todo = ({ todo, completeFn }) => {
  const handleOnclick = useCallback(() => {
    // useCallback since function is passed down from parent
    console.log(`completing todo...`);
    completeFn(todo);
  }, [completeFn, todo]);

  return (
    <div>
      <input
        type="checkbox"
        name={todo.id}
        id={todo.id}
        onClick={handleOnclick}
      />
      <label className="form-check-label" htmlFor={todo.id}>
        {todo.description}
      </label>
    </div>
  );
};

class TodoService {
  constructor(todos) {
    this.todos = new Map();
    todos.forEach(todo => {
      this.todos.set(todo.id, todo);
    });
  }

  findAll() {
    console.log(Array.from(this.todos.values()));
    return Array.from(this.todos.values());
  }

  saveTodo(todo) {
    this.todos[todo.id] = todo;
  }

  completeTodo(todo) {
    this.todos.delete(todo.id);
  }
}

const todoService = new TodoService([
  {
    id: 1,
    description: "Let's go home."
  },
  {
    id: 2,
    description: "Take down the trash"
  },
  {
    id: 3,
    description: "Play games"
  }
]);

export default function App() {
  const [todos, setTodos] = useState([]); // Set initial state

  const completeTodo = todo => {
    todoService.completeTodo(todo);
    setTodos(todoService.findAll()); // Update state
  };

  useEffect(() => {
    setTodos(todoService.findAll());
  }, []); // Get and update from service on first render only

  return (
    <div>
      {todos.map(todo => (
        <Todo key={todo.id} todo={todo} completeFn={completeTodo} />
      ))}
    </div>
  );
}

рабочий пример

https://codesandbox.io/s/cranky-hertz-sewc5?file= / src / App. js

...