Home > Net >  How to make child communicate with parent in this scenario react
How to make child communicate with parent in this scenario react

Time:12-03

Im making a quiz app. Currently i can switch to the next question by answering (clicking a button). Also, when timer expires (10 secs for each question) it automatically switches to the next question (timer resets to default state) regardless of button clicked or no. Above mentioned things works and its fine.

However I made a timer as a seperate child component to avoid unneccesary rerenders of parent component. The problem is that when i click a button to answer the question timer does not reset (but it should) to its default state value (10secs). I dont know how to communicate between child (Timer) and parent component (App), because i dont wanna initialize that timer state in parent component and put it in handleAnswerCorrectness so when currentPage changes i also reset timer, because it would cause parent rerender each second and lose sense of making it a child component.

How to do it properly???

const {useState, useEffect} = React;

const questions = [
  {
    questionText: "What is the capital city of France",
    answerOptions: [
      { id: 1, answerText: "New York", isCorrect: false },
      { id: 2, answerText: "London", isCorrect: false },
      { id: 3, answerText: "Paris", isCorrect: true },
      { id: 4, answerText: "Dublin", isCorrect: false }
    ]
  },
  {
    questionText: "Who is CEO of Tesla?",
    answerOptions: [
      { id: 1, answerText: "Jeff Bezos", isCorrect: false },
      { id: 2, answerText: "Elon Musk", isCorrect: true },
      { id: 3, answerText: "Bill Gates", isCorrect: false },
      { id: 4, answerText: "Tony Stark", isCorrect: false }
    ]
  }
];

const App = () =>  {
  const [currentPage, setCurrentPage] = useState(0);

  const handleAnswerCorrectness = () => {
    if (currentPage   1 < questions.length) {
      setCurrentPage((prevState) => prevState   1);
    }
  };

  return (
    <div className="App">
      <div>
        <h3>
          Question {currentPage   1}/{questions.length}
        </h3>
        <Timer
          currentPage={currentPage}
          onCurrentPageChange={setCurrentPage}
          questionsLenght={questions.length}
        />
      </div>
      <p>{questions[currentPage].questionText}</p>
      <div>
        {questions[currentPage].answerOptions.map((answerOption) => (
          <button key={answerOption.id}  onClick={handleAnswerCorrectness}>
            {answerOption.answerText}
          </button>
        ))}
      </div>
    </div>
  );
}

const Timer = ({
  onCurrentPageChange,
  currentPage,
  questionsLenght
}) => {
  const [timer, setTimer] = useState(10);

  useEffect(() => {
    const interval = setInterval(() => {
      if (timer > 0) {
        setTimer((prevState) => prevState - 1);
      }
      if (timer === 0 && currentPage   1 < questionsLenght) {
        onCurrentPageChange((prevState) => prevState   1);
        setTimer(10);
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [timer]);

  return (
    <div>
      <span>{timer}</span>
    </div>
  );
};


const root = ReactDOM.createRoot(
  document.getElementById("root")
).render(<App/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

CodePudding user response:

It seems that the useEffect in Timer is both setting state timer and reading it in dependencies array, which could cause a conflict.

Perhaps try separating it to 2 useEffect with more simple logic, so related code will only run when needed.

One for the purpose of countdown, and reset timer when currentPage changes:

// Countdown effect
useEffect(() => {
  setTimer(10);
  const interval = setInterval(
    () =>
      setTimer((prevState) => {
        if (prevState > 0) return prevState - 1;
        return prevState;
      }),
    1000
  );

  return () => clearInterval(interval);
}, [currentPage]);

Another to handle timeout and trigger onCurrentPageChange:

// Timeout effect
useEffect(() => {
  if (timer === 0 && currentPage   1 < questionsLenght)
    onCurrentPageChange((prevState) => prevState   1);
}, [timer, currentPage, questionsLenght]);

There might be other issues that needed to be addressed, but hopefully this would still help by making the logic cleaner.

Example:

const { useState, useEffect } = React;

const questions = [
  {
    questionText: "What is the capital city of France",
    answerOptions: [
      { id: 1, answerText: "New York", isCorrect: false },
      { id: 2, answerText: "London", isCorrect: false },
      { id: 3, answerText: "Paris", isCorrect: true },
      { id: 4, answerText: "Dublin", isCorrect: false },
    ],
  },
  {
    questionText: "Who is CEO of Tesla?",
    answerOptions: [
      { id: 1, answerText: "Jeff Bezos", isCorrect: false },
      { id: 2, answerText: "Elon Musk", isCorrect: true },
      { id: 3, answerText: "Bill Gates", isCorrect: false },
      { id: 4, answerText: "Tony Stark", isCorrect: false },
    ],
  },
];

const App = () => {
  const [currentPage, setCurrentPage] = useState(0);

  const handleAnswerCorrectness = () => {
    if (currentPage   1 < questions.length) {
      setCurrentPage((prevState) => prevState   1);
    }
  };

  return (
    <div className="App">
      <div>
        <h3>
          Question {currentPage   1}/{questions.length}
        </h3>
        <Timer
          currentPage={currentPage}
          onCurrentPageChange={setCurrentPage}
          questionsLenght={questions.length}
        />
      </div>
      <p>{questions[currentPage].questionText}</p>
      <div>
        {questions[currentPage].answerOptions.map((answerOption) => (
          <button key={answerOption.id} onClick={handleAnswerCorrectness}>
            {answerOption.answerText}
          </button>
        ))}
      </div>
    </div>
  );
};

const Timer = ({ onCurrentPageChange, currentPage, questionsLenght }) => {
  const [timer, setTimer] = useState(10);

  // Countdown effect
  useEffect(() => {
    setTimer(10);
    const interval = setInterval(
      () =>
        setTimer((prevState) => {
          if (prevState > 0) return prevState - 1;
          return prevState;
        }),
      1000
    );

    return () => clearInterval(interval);
  }, [currentPage]);

  // Timeout effect
  useEffect(() => {
    if (timer === 0 && currentPage   1 < questionsLenght)
      onCurrentPageChange((prevState) => prevState   1);
  }, [timer, currentPage, questionsLenght]);

  return (
    <div>
      <span>{timer}</span>
    </div>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root")).render(
  <App />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

  • Related