skip to Main Content

I know there are multiple questions on this topic already but I didn’t get the gist from them. I’m tired of asking this on chatGPT and Bard so that’s why I’m here.

Lil Background – I’m a web designer learning development, so yeah completely beginner. I would appreciate your help, guys.

Lil background on what I’m trying to do here – I’m building a quiz app. The state of button is not updating immediately on clicking the button.

Here is the code –

import React, { useState } from "react";
import { QuestionsData } from "../data/QuestionsData";
import AlertBox1 from "./modals/AlertBox-1";
import "../css/question.css";

export default function Question() {
  // State variable declared
  // Array destructuring syntax is used
  // const [selectedOption, setSelectedOption] = useState("");
  const [alertBox1, setAlertBox1] = useState(false);
  const [currentQuestion, setCurrentQuestion] = useState(0);
  const [clickedOption, setClickedOption] = useState(0);

  const changeQuestion = () => {
    
    if (currentQuestion < QuestionsData.length - 1) {
      setCurrentQuestion(currentQuestion + 1);
    }

    else {}
  };

  const handleClick = (i) => {
    setClickedOption(i + 1);
  }

  const checkAnswer = () => {
    if (clickedOption === QuestionsData[currentQuestion].answer) {
      alert("This is right answer");
    }

    else {
      alert("This is wrong answer")
    }
  }

  const closeAlertBox1 = () => {
    setAlertBox1(false)
  }

  return (
    <>
      <div className="question">
        <h1>
          <span className="question-number">{currentQuestion + 1}. </span>
          <span className="question-txt">
            {QuestionsData[currentQuestion].question}
          </span>
        </h1>

        {/* onClose is a prop here */}
        {alertBox1 && <AlertBox1 onClose={closeAlertBox1} />}
      </div>
      <div className="options-container">
        {QuestionsData[currentQuestion].options.map((option, i) => {
          return <button key={i}onClick={() => handleClick(i)}>{option}</button>;
        })}
      </div>

      <input type="button" value="Next" onClick={changeQuestion}/>
    </>
  );
}

2

Answers


  1. Completely unrelated to your issues, but since you’re learning React, I figured I’d help you along.

    First off, make sure you memoize complex values like big objects or functions.

    Notice how every time your component is rendered, you’re declaring functions like checkAnswer. Although JS can optimize these out at times, you should definitely make use of React’s memoization hooks like useMemo and useCallback. These define your functions/values once and only redefine upon a change in the dependencies you supply (for instance, the variables you use in a callback). A good tool to figure out which dependencies are needed is the react-hooks plugin for eslint with its exhaustive-dependencies rule.

    Just to show you, checkAnswer would look like this

      const checkAnswer = useCallback(() => {
        if (clickedOption === QuestionsData[currentQuestion].answer) {
          alert("This is right answer");
        }
    
        else {
          alert("This is wrong answer")
        }
      }, [clickedOptions, currentQuestion])
    /* Note that `QuestionsData` doesn't need to be supplied because it's imported any doesn't change. Things that change such as states or props DO need to be supplied. */
    

    Furthermore, applying this to the changeQuestion callback presents an issue. You would have to make the callback depend on currentQuestion which is a very unnecessary dependency since all you’re doing is incrementing. Some people like to use a reducer for this instead of a state, however, you don’t need to because setState also takes a callback.

    You can use

    setCurrentQuestion((prev) => prev + 1)
    

    I wish you luck in your React endeavors

    Login or Signup to reply.
  2. The point of React is this:

    Your views are pure functions of your application state.

    Think about what that means you can’t do: it means you can’t do arbitrary side effects like writing to the DOM. And if you can’t manipulate the DOM, which is what’s shown on the page, that means that something else must be doing it for you, otherwise what’s shown on the page would never change.

    That something in this case is the React runtime. You tell React what you want the page to look like, it calculates what the DOM would need to be to make that happen, calculates the difference between that and the existing DOM state, and performs the necessary changes for you.

    That’s a lot of work.

    In order to make that work happen in a reasonable amount of time, it makes sense to optimize it. For instance if you have a state update that gets overwritten almost immediately by another one rather than do the whole song and dance twice it makes more sense to just do it with LWW. So React batches the state changes: when you call setState you’re just queueing an update for the runtime to calc, diff, and write. React will apply all those changes to the application state, and then do the work.

    Where It All Went Wrong For You

    As a corollary to all of this you can wind up with non-atomic changes. In your code say the user clicks the button twice rapidly. React will queue up "set count to 1" and then another "set count to 1". Why? Because when the second event handler runs the state change from the first one hasn’t been applied yet, so the count is still zero.

    So setState and the setter from useState offer a callback version for updates that depend on the previous state: setState((prev) => next). This version will always be passed the updated state as a parameter, to avoid bugs like the one I just described. Because in this version rather than queue up "set count to 1" it queues up "set count to whatever this callback returns". The number 1 is a static value, but the callback is dynamic: its value is determined when the state updates are processed rather than when the click handler fires.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search