Я создаю таймер с помощью крючков React. Это простой компонент, который используется в викторине. Каждый вопрос имеет определенную продолжительность, поэтому таймер должен запускаться с этой продолжительности и начинать уменьшаться на одну секунду за раз. Моя проблема в том, что компонент делает то, что должен делать, но когда я перехожу к следующему вопросу, состояние не инициализируется продолжительностью, переданной в реквизитах, а продолжается со счетчиком ...

  const Timer = ({ duration }) => {
  const [counter, setCounter] = useState(duration);
  const timer = useRef();
  debugger;
  console.log("counter: " + counter);
  const setTimer = () => {
    if (counter <= duration) {
      setCounter(counter - 1);
    }
  };
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    timer.current = setTimeout(() => setTimer(), 1000);
    return () => {
      if (timer.current) {
        console.log("ClearInterval in Timer");
        clearTimeout(timer.current);
      }
    };
  }, [counter]);
  return (
    <div>
      <p>time left {counter} seconds</p>
    </div>
  );
};

export default Timer;

Я визуализирую компонент Timer из компонента Card:

import React from "react";
import QuizButton from "../QuizButton/QuizButton";
import Timer from "../Timer/Timer";
import "./styles.css";

const QuizQuestion = ({ question, responses, checkAnswerFn, duration }) => (
  <article className='card'>
    <header>
      <Timer duration={duration} />
    </header>
    <div>
      <p>{question}</p>
    </div>
    <footer>
      {responses.map((response, i) => {
        return (
          <QuizButton key={i} onClick={checkAnswerFn(response)}>
            {response}
          </QuizButton>
        );
      })}
    </footer>
  </article>
);

export default QuizQuestion;

Кто-нибудь знает, почему состояние не инициализируется длительностью после повторного рендеринга вопроса?

0
Javip 18 Авг 2020 в 18:58

4 ответа

Лучший ответ

ОБНОВЛЕНО 2. Добавлены массив вопросов и данные таймера на основе вопросов

ОБНОВЛЕНО 1. Я ДУМАЮ, ЧТО СЕЙЧАС ЭТО РАБОТАЕТ, КАК ВЫ ХОТИТЕ ИМЕТЬ

Резюме - я поднял некоторые состояния.

Итак, отредактированный ответ: поскольку нам нужно повторно отправить новые данные опоры в компонент дочернего таймера, в то время как триггер был основан на дочернем компоненте, который я продемонстрировал с помощью щелчка из дочернего компонента таймера, мы передали функцию из родительского компонента вопроса. в дочерний компонент, который будет вызываться в прослушивателе onClick через опору. Поэтому функция обновляет состояние родителя (Question.js), повторно отрисовывая дочерний элемент (Timer.js) и передавая новые значения свойств, которые мы установили как новые. Вместо того, чтобы передавать изменения на основе таймера, мы также перешли на триггер на основе данных, например, данные вопроса, измененные для вызова useEffect.

Я надеюсь, что объяснение работает и решило ваш вариант использования.

..

//Question.js    

import React, { useState, useEffect } from "react";
import Timer from "./Timer";
import "./styles.css";

const Question = () => {
  const questionPool = [
    { number: 1, text: "lorem", timer: 6 },
    { number: 2, text: "lorem", timer: 10 },
    { number: 3, text: "lorem", timer: 20 },
    { number: 4, text: "lorem", timer: 3 },
    { number: 5, text: "lorem", timer: 12 }
  ];
  const [countDown, setCountDown] = useState(questionPool[0].timer);
  const [currentQuestion, setCurrentQuestion] = useState(
    questionPool[0].number
  );


  const resetCount = (newQuestion) => {
    newQuestion < questionPool.length + 1
      ? setCurrentQuestion(newQuestion)
      : setCurrentQuestion(1);
  };

  useEffect(() => {
    if (questionPool[currentQuestion]) {
      setCountDown(questionPool[currentQuestion].timer);
    } else {
      setCountDown(questionPool[0].timer);
    }
  }, [currentQuestion]);

  return (
    <>
      <Timer
        duration={countDown}
        resetCountDown={resetCount}
        questionNumber={currentQuestion}
      />
    </>
  );
};

export default Question;

....

//Timer.js

import React, { useEffect, useState } from "react";
import "./styles.css";

const Timer = ({ duration, resetCountDown, questionNumber}) => {
  const [counter, setCounter] = useState(duration);
  let interval = null;
  useEffect(() => {
    const myInterval = () => {
      if (counter > 1) {
        setCounter((state) => state - 1);
      } else if (counter !== 0) {
        setCounter(0);
        clearInterval(interval);
      }
    };
    interval = setInterval(myInterval, 1000);
    return () => {
      clearInterval(interval);
    };
  }, [counter]);

  useEffect(() => {
    if (!interval) {
      setCounter(duration);
    }
  }, [questionNumber]);

  return (
    <div>
      <p>
        time left {counter} seconds For Question: {questionNumber}
      </p>
      <button type="button" onClick={() => resetCountDown(questionNumber + 1)}>
        next
      </button>
    </div>
  );
};

export default Timer;

CodeSandbox Расширяется от @Adolfo Onrubia для соответствия вашему варианту использования

Проверьте, помогает ли это.

********* УСТАРЕЛО НИЖЕ *******************

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

Короче говоря,

  • Ваш массив вопросов [{ОБЪЕКТ С ДЕТАЛЯМИ ВОПРОСА}] находится в вашем основном приложении.
  • У вас есть useState, например:

const [currentQuestion, setCurrentQuestion] = useState (QuestionArray [0]);

И всякий раз, когда нажимается следующая кнопка. Вы делаете

setCurrentQeuration (QuestionArray [NewIndex]);

Таким образом, состояние компонентов приложения вашего главного компонента повторно отображает useEffect из currentQuestion и передает его дочернему компоненту Timer.js, вызывая его повторную визуализацию с новой продолжительностью тайм-аута.

Также может быть целесообразно проверить props.children, поэтому вместо того, чтобы просто отправлять как обычную опору, вы передаете как шаблон, например.

<Template>
 {currentQuestion}
</Template>
0
Fauzul Chowdhury 20 Авг 2020 в 18:00

Условие в функции setTimer не позволяет инициализировать counter. Пусть ваша функция уменьшает счетчик до тех пор, пока значение счетчика не будет больше 0, в противном случае инициализируйте его с помощью duration. Замените вашу функцию следующей, это поможет вам инициализировать таймер обратно на продолжительность.

const setTimer = () => {
  setCounter(counter > 0  ? counter - 1 : duration);
};
1
Awais Rafiq Chaudhry 18 Авг 2020 в 16:17

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

const [counter, setCounter] = useState(15000);

...


useEffect(() => {
    const myInterval = () => {
      if (counter > 1000) {
        setCounter(state => state - 1000)
      } else if (counter !== 0){
        setCounter(0);
        clearInterval(interval);
      }
    }
    const interval = setInterval(myInterval, 1000);
    return () => {
      clearInterval(interval)
    }
  }, [counter]);

...

console.log(counter); // 15000, 14000, 13000, ...
1
Adolfo Onrubia 18 Авг 2020 в 16:10

Благодаря Фаузулу и Адольфо я наконец решил свою проблему.

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

QuizQuestion.js

import React from "react";
import PropTypes from "prop-types";
import QuizButton from "../QuizButton/QuizButton";
import Timer from "../Timer/Timer";
import "./styles.css";

const QuizQuestion = ({ question, responses, checkAnswerFn, duration, id }) => (
  <article className='card'>
    <header>
      <Timer duration={duration} id={id} />
    </header>
    <div>
      <p>{question}</p>
    </div>
    <footer>
      {responses.map((response, i) => {
        return (
          <QuizButton key={i} onClick={checkAnswerFn(response)}>
            {response}
          </QuizButton>
        );
      })}
    </footer>
  </article>
);

QuizQuestion.propTypes = {
  question: PropTypes.string.isRequired,
  responses: PropTypes.array.isRequired,
  checkAnswerFn: PropTypes.func.isRequired,
};

export default QuizQuestion;

Timer.js

import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";

const Timer = ({ duration, id }) => {
  const [counter, setCounter] = useState(duration);
  const timer = useRef();
  const setTimer = () => {
    if (counter > 0) {
      setCounter(counter - 1);
    }
  };
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    timer.current = setTimeout(() => setTimer(), 1000);
    return () => {
      if (timer.current) {
        clearTimeout(timer.current);
      }
    };
  }, [counter]);

  useEffect(() => {
    setCounter(duration);
  }, [id]);

  return (
    <div>
      <p>time left {counter} seconds</p>
    </div>
  );
};

Timer.propTypes = {
  duration: PropTypes.number.isRequired,
  id: PropTypes.number.isRequired,
};

export default Timer;
0
Javip 20 Авг 2020 в 14:34