Search code examples
reactjsdisableuse-ref

Prevent an onClick event on a span element when it was clicked once


I'm building a game where you have to click on a cross pictogram or check pictogram depending on whether the answer were correct or false. But as soons as you have answered you can't click again on none of the two pictograms (even on the one you didn't click on).

In order to do this, I used useRef on the element that contains the pictograms and I tried to make the element disabled. It didn't work. I've also tried to make their parent element "uneventable" but it didn't work neither

I would appreciate any help. thank you

const pictoAnswer = useRef()
const divParent = useRef()

pictoAnswer.current.disabled = true
divParent.current.pointerEvents = "none"

overall view

import React, { useState, useRef } from "react";

// modules
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";

const WordList = () => {
  const liBgColor = useRef();
  const pictoAnswer = useRef();
  const divParent = useRef();

  // change the color bg of the list element
  const isCorrect = (answer) => {
    if (answer) {
      liBgColor.current.style.backgroundColor = "#5DCF36";
    } else {
      liBgColor.current.style.backgroundColor = "#d15f5f";
    }
  };

  //list of word to make guess
  let wordsToGuess = ["chambre", "salon", "rire", "fusée"];
  const numberOfWordToGuess = wordsToGuess.length;

  const [indexWord, setIndexWord] = useState(1);
  console.log("indexWord:", indexWord);
  const [wordToDisplay, setWordToDisplay] = useState([wordsToGuess[0]]);

 
  // push the next word to guess into the wordToDisplay array
  const handleAnswer = (answer) => {
   

    pictoAnswer.current.disabled = true;
    divParent.current.pointerEvents = "none";
    //set the new index of the word item
    setIndexWord(indexWord + 1);
    //change the bgColor depending on the answer
    isCorrect(answer);
    //would display the next word or not
    if (indexWord == numberOfWordToGuess || indexWord > numberOfWordToGuess) {
      console.log("no more word to guess");
    } else {
      setWordToDisplay([...wordToDisplay, wordsToGuess[indexWord]]);
    }
  };

  return (
    <ul className="word__list">
      {wordToDisplay.map((word, i) => (
        <li key={i} className="word__item" ref={liBgColor}>
          <p className="word">{word}</p>
          <div className="icon-box" ref={divParent}>
            <span
              ref={pictoAnswer}
              onClick={() => {
                handleAnswer(false);
              }}
            >
              <FontAwesomeIcon icon={faTimes} color={"#FF5252"} />
            </span>
            <span
              ref={pictoAnswer}
              onClick={() => {
                handleAnswer(true);
              }}
            >
              <FontAwesomeIcon icon={faCheck} color={"#008000"} />
            </span>
          </div>
        </li>
      ))}
    </ul>
  );
};

export default WordList;

codeSandBox


Solution

  • The main issue with your code is incorrect use of React refs to manipulate DOM elements.

    Solution

    1. Store a "settled" state array initialized to null values to indicate if a word has been marked true/false. Same condition is used to prevent further "click" handling.
    2. Use CSS to style the list item.
    3. Update handleAnswer to set the "settled" state and increment the word index.
    4. Use an useEffect hook to update the words to display.

    Code:

    const wordsToGuess = ["chambre", "salon", "rire", "fusée"];
    
    const App = () => {
      const [indexWord, setIndexWord] = useState(0);
      const [wordToDisplay, setWordToDisplay] = useState([]);
      const [settled, setSettled] = useState(Array(wordsToGuess.length).fill(null));
    
      useEffect(() => {
        setWordToDisplay(wordsToGuess.slice(0, indexWord + 1));
      }, [indexWord]);
    
      const handleAnswer = (index, answer) => () => {
        if (settled[index] === null) {
          setSettled((settled) =>
            settled.map((el, i) => (i === indexWord ? answer : el))
          );
          if (indexWord < wordsToGuess.length - 1) setIndexWord((i) => i + 1);
        }
      };
    
      return (
        <ul className="word__list">
          {wordToDisplay.map((word, i) => (
            <li
              key={i}
              className={classNames("word__item", {
                "settled-true": settled[i] === true,
                "settled-false": settled[i] === false
              })}
            >
              <p className="word">{word}</p>
              <div className="icon-box">
                <span onClick={handleAnswer(i, false)}>
                  <FontAwesomeIcon icon={faTimes} color={"#FF5252"} />
                </span>
                <span onClick={handleAnswer(i, true)}>
                  <FontAwesomeIcon icon={faCheck} color={"#008000"} />
                </span>
              </div>
            </li>
          ))}
        </ul>
      );
    };
    

    CSS:

    .settled-true {
      background-color: #5dcf36;
    }
    
    .settled-false {
      background-color: #d15f5f;
    }
    

    Demo

    Edit prevent-an-onclick-event-on-a-span-element-when-it-was-clicked-once