Визуализация компонентов с использованием .map сбрасывает setTimeout внутри компонентов - PullRequest
1 голос
/ 08 октября 2019

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

Я пытаюсь использовать этот компонент повторителя для рендеринга другого компонента, в котором есть setTimeout. Если вы добавляете или удаляете другой компонент, setTimouts всех других компонентов сбрасывается, и поэтому все они запускаются в одно и то же время.

Используемый мной компонент ретранслятора:

class Repeater extends React.Component {
  state = {
    elements: [],
  };

  component = Notification; // For simplicity - actually dynamic from props
  count = 0;

  getKey = () => {
    return this.count++;
  };

  add = props => {
    const { elements } = this.state;    
    props.key = this.getKey();
    elements.push(props);

    this.setState({ elements });
  };

  remove = key => {
    const { elements } = this.state;
    const newElements = elements.filter(element => {
      return element.key !== key;
    });

    this.setState({ elements: newElements });
  };

  clear = () => {
    this.setState({ elements: [] });
  };

  render() {
    const { elements } = this.state;
    const Component = this.component;

    return (
      <React.Fragment>
        {elements.map(element => {
          return <Component key={element.key} {...element} />;
        })}
      </React.Fragment>
    );
  }
}

Компонент, который я рендеринг:

export function Notification() {
  const {
    isOpen = true,
    message,
    title,
    duration = 4.5,
  } = props;
  const [openState, setOpenState] = React.useState(isOpen);
  let timer;

  const clearTimer = () => {
    window.clearTimeout(timer);
  };

 const handleClose = () => {
    clearTimer();
    console.log('Test');
  };

  const setTimer = () => {
    if (duration) timer = setTimeout(() => handleClose(), duration * 1000);
  };

  if (openState) {
    setTimer();
    return (
      <div
        onMouseEnter={() => clearTimer()}
        onMouseLeave={() => setTimer()}>
        <div>
          <Icon />
        </div>
        <div>{title}</div>
        <div>{message}</div>
      </div>
    );
  }
  return null;
}

Ответы [ 2 ]

0 голосов
/ 09 октября 2019

Мне удалось решить эту проблему, сохранив время рендеринга компонента уведомления в состоянии при первой загрузке и обнаружив разницу между ним и текущим временем в моем методе setTimer.

...

const [renderTime] = React.useState(Date.now());

...

const setTimer = () => {
  if (duration) {
    const elapsed = Date.now() - openTime;
    const timeout = duration - elapsed;
    timer = setTimeout(() => handleClose(), timeout);
  }
};
0 голосов
/ 08 октября 2019

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

Я обновил решение, чтобы использовать новейшие функции ES6 +. Есть еще что-то, что можно пожелать от функциональности, которой вы хотите достичь, и полного контекста вашего кода, но вот первая итерация, которая должна работать, так как функции setState ссылаются на предыдущее состояние.

import React, { useState, useEffect } from 'react';

export function Notification({ duration=4500, isOpen=true, message, title }) {
  const [open, setOpen] = useState(isOpen);

  let timer;

  useEffect(() => {
    setTimer();
  });

  const handleClose = () => {
    clearTimer();
  };

  const clearTimer = () => {
    window.clearTimeout(timer);
  };

  const setTimer = () => {
    timer = setTimeout(() => handleClose(), duration);
  };

  if (!open) return null;

  return (
    <div
      onMouseEnter={() => clearTimer()}
      onMouseLeave={() => setTimer()}>
      <div>
        <Icon />
      </div>
      <div>{title}</div>
      <div>{message}</div>
    </div>
  );
};

export function Repeater({ component }) {
  const [elements, setElements] = useState([]);
  const [count, setCount] = useState(0);

  const Component = component ? component : Notification;

  const getKey = () => {
    setCount(count => count + 1);
    return count;
  };

  const add = (props) => {
    setElements((elements) => elements.push({ ...props, key: getKey }));
  };

  const remove = (key) => {
    setElements((elements) => elements.filter((e) => e.key !== key));
  };

  const clear = () => {
    setElements([]);
  };

  const renderedElements = elements.map(({ key, ...rest }) => (
    <Component key={key} {...rest} />
  ));

  return (
    <>
      {renderedElements}
    </>
  );
};
...