React Hooks несколько предупреждений с индивидуальными обратными отсчетами - PullRequest
3 голосов
/ 26 июня 2019

Я пытался создать приложение React с несколькими предупреждениями, которые исчезают через определенное время. Образец: https://codesandbox.io/s/multiple-alert-countdown-294lc

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

import "./styles.css";

function TimeoutAlert({ id, message, deleteAlert }) {
  const onClick = () => deleteAlert(id);
  useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  });
  return (
    <p>
      <button onClick={onClick}>
        {message} {id}
      </button>
    </p>
  );
}
let _ID = 0;
function App() {
  const [alerts, setAlerts] = useState([]);
  const addAlert = message => setAlerts([...alerts, { id: _ID++, message }]);
  const deleteAlert = id => setAlerts(alerts.filter(m => m.id !== id));
  console.log({ alerts });
  return (
    <div className="App">
      <button onClick={() => addAlert("test ")}>Add Alertz</button>
      <br />
      {alerts.map(m => (
        <TimeoutAlert key={m.id} {...m} deleteAlert={deleteAlert} />
      ))}
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Проблема в том, что если я создаю несколько оповещений, они исчезают в неправильном порядке. Например, тест 0, тест 1, тест 2 должны исчезнуть, начиная с теста 0, теста 1 и т. Д., Но вместо этого тест 1 исчезает первым, а тест 0 исчезает последним.

Я продолжаю видеть ссылки на useRefs, но мои реализации не устраняют эту ошибку.


С помощью @ ehab я думаю, что смог направиться в правильном направлении. В моем коде я получил дополнительные предупреждения о добавлении зависимостей, но из-за дополнительных зависимостей мой код стал работать некорректно. В конце концов я понял, как использовать ссылки. Я преобразовал его в пользовательский хук.

function useTimeout(callback, ms) {
  const savedCallBack = useRef();
  // Remember the latest callback
  useEffect(() => {
    savedCallBack.current = callback;
  }, [callback]);
  // Set up timeout
  useEffect(() => {
    if (ms !== 0) {
      const timer = setTimeout(savedCallBack.current, ms);
      return () => clearTimeout(timer);
    }
  }, [ms]);
}

Ответы [ 2 ]

3 голосов
/ 26 июня 2019

У вас две проблемы с вашим кодом,

1) использование вами эффекта означает, что эта функция будет вызываться при каждом рендеринге компонента, однако, очевидно, что в зависимости от вашего варианта использования вы хотите, чтобы эта функция вызывалась один раз, поэтому измените ее на

 useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  }, []);

добавление пустого массива в качестве второго параметра означает, что ваш эффект не зависит от какого-либо параметра, и поэтому его следует вызывать только один раз.

Ваше предупреждение об удалении зависит от значения, которое было записано при создании функции, это проблематично, поскольку в это время у вас нет всех предупреждений в массиве, измените его на

const deleteAlert =  id => setAlerts(alerts => alerts.filter(m => m.id !== id));

вот ваш пример работы после того, как я его разветвил https://codesandbox.io/s/multiple-alert-countdown-02c2h

0 голосов
/ 26 июня 2019

Что ж, ваша проблема в том, что вы перемонтируете при каждом повторном рендеринге, поэтому в основном вы сбрасываете свои таймеры для всех компонентов во время рендеринга.

просто чтобы прояснить это, попробуйте добавить {Date.now()} в ваши компоненты Alert

      <button onClick={onClick}>
        {message} {id} {Date.now()}
      </button>

вы заметите сброс каждый раз

, поэтому для достижения этого в функциональных компонентах необходимо использовать React.memo

пример, чтобы ваш код работал, я бы сделал:

const TimeoutAlert = React.memo( ({ id, message, deleteAlert }) => {
  const onClick = () => deleteAlert(id);
  useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  });
  return (
    <p>
      <button onClick={onClick}>
        {message} {id}
      </button>
    </p>
  );
},(oldProps, newProps)=>oldProps.id === newProps.id) // memoization condition

2-й исправить ваше useEffect, чтобы не запускать функцию очистки на каждом рендере

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

наконец-то, что касается вкуса, но действительно ли вам нужно уничтожить объект {...m}? я бы сказал, что это правильная опора, чтобы каждый раз не создавать новый объект!

...