Robozombie
Robozombie

Reputation: 37

Targeting only the clicked item on a mapped component ( React quiz trivia App)

i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code: in the main APP.js

  const [clickedOne, setClickedOne] = useState({
    clickedIndex: null,
  });

  useEffect(() => {
    grabData();
  }, []);

  const handleClick = (choice, ke) => {
    setChoice(choice);

    if (choice === data.correct_answer) {
      setIsCorrect(true);
    } else {
      setIsCorrect(false);
    }
    setClickedOne({ clickedIndex: ke });

    grabData();
  };

The mapped button inside the Render:

      {answers.map((answer, index) => {
          return (
            <ContainedButtons
              choice={handleClick}
              answer={answer}
              correct={data.correct_answer}
              isCorrect={isCorrect}
              key={index}
              id={index}
              clicked={clickedOne}
            />
          );
        })}

Inside the Button component:

const backStyle = () => {
    if (clicked === id) {
      if (isCorrect) {
        return "green";
      } else if (isCorrect === false) {
        return "red";
      } else {
        return null;
      }
    }
  };

  return (
    <div className={classes.root}>
      <Button
        style={{ backgroundColor: backStyle() }}
        value={answer}
        onClick={() => choice(answer, id)}
        variant="contained"
      >
        {decodeURIComponent(answer)}
      </Button>

When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green. Thank you guys for the help!

Upvotes: 1

Views: 220

Answers (1)

Yousaf
Yousaf

Reputation: 29344

I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.

First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.

"https://opentdb.com/api.php?amount=1&encode=url3986"

Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers

const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);

Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.

To achieve the desired functionality, take following steps:

  1. create couple of CSS classes as shown below

    button.bgGreen {
      background-color: green !important;
    }
    
    button.bgRed {
      background-color: red  !important;
    }
    
  2. pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.

  3. Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.

Here's a working codesanbox demo of your app with the above mentioned steps.

In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.

Upvotes: 1

Related Questions