22

So, What I'm trying to do is add an event listener to a button that when pressed takes the value of a state and console log it. But the logged value never updates even after the multiple setState calls.

    useEffect(() => {
      const seamCarve = document.getElementById("process");
    
      seamCarve.addEventListener("click", (e) => {
        console.log(seamValue);
      });
    }, []);

And then there's the button that triggers it

          <div id="seamvalue">
            <Typography variant="h6" align="center">
              Seams Reduction:<span id="valueOfSeam"> {seamValue} </span>
            </Typography>
          </div>
    
          <Button
            id="leftslider"
            variant="contained"
            color="primary"
            onClick={() =>
              seamValue - 10 > 0 ? setSeamValue(seamValue - 10) : setSeamValue(0)
            }
          >
            <Typography>{"<"}</Typography>
          </Button>
    
          <Button
            id="rightslider"
            variant="contained"
            color="primary"
            onClick={() => setSeamValue(seamValue + 10)}
          >
            <Typography>{">"}</Typography>
          </Button>
    
          <Button id="process" variant="contained" color="primary">
            <Typography>Carve</Typography>
          </Button>

The value changes as I click on the sliders but when I press the Button with id="process" with an event listener associated with it, it only prints 20 even after updating the state.

sonique
  • 4,539
  • 2
  • 30
  • 39
Akash
  • 331
  • 1
  • 2
  • 6

3 Answers3

21

Don't use native DOM methods like addEventListener in React if at all possible - instead, work within React to achieve the same effect. It'll make a whole lot more sense and will require less convoluted code.

Put the click listener in the JSX syntax returned instead.

<Button
    id="process"
    variant="contained"
    color="primary"
    onClick={() => console.log(seamValue)}
>
    <Typography>Carve</Typography>
</Button>

If you absolutely cannot attach the click listener via React for some reason, then attach the listener again when the state changes. Also note that in order to start a function, you need a { after the =>, unless you're using concise return (which you aren't here).

useEffect(() => {
    const seamCarve = document.getElementById("process");
    const handler = () => {
        console.log(seamValue);
    };
    seamCarve.addEventListener('click', handler);
    return () => seamCarve.removeEventListener('click', handler);
}, [seamValue]);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    Oh, I didn't notice OP was using the "onClick" event listener. Then my answer just applies to the general use of `document.addEventListener`. – FireFighter Apr 24 '21 at 15:05
  • The problem i have is that i need access to certain elements that I can't get access if I use that. – Akash Apr 24 '21 at 15:08
  • @Akash Why can't you access it? If the state(s) you need isn't in the component where you click on the button, put the state in a parent component instead and pass it down. – CertainPerformance Apr 24 '21 at 15:09
  • I used a very non reactive way to add a bunch of event listeners because I'm primarily working with HTML Canvas and I need more flexibility than the wrappers provide. The useEffect hook with no dependencies adds event listener that load the image and then draw the loaded image on canvas. – Akash Apr 24 '21 at 15:18
8

State variables don't update itself inside an EventListener. If you want to update and access some state inside an EventListener, look into useRef. Also, you need to replace your 2nd useState statement with useEffect since that's what you wanted to implement. A relevant post that should answer your question: Wrong React hooks behaviour with event listener

EDIT: as @CertainPerformance stated, event listeners like onClick can be directly used inside the jsx. My answer applies to the use of event listeners on the document.

FireFighter
  • 686
  • 3
  • 6
0

In my case I was looking for a solution to use the selectionchange event and set the state accordingly. I found out you can pass the state and setState function via args to another function and it works perfectly

  const [selectedText, setSelectedText] = useState('')

  function handleStateChange(func, value) {
    if (func) {
      func(value)
    }
  }

  useEffect(() => {
    const handleSelectionChange = () => {
      const selectedText = window.getSelection().toString()
      handleStateChange(setSelectedText, selectedText)
    }

    document.addEventListener('selectionchange', handleSelectionChange)

    return () => {
      document.removeEventListener('selectionchange', handleSelectionChange)
    }
  }, [])
vilmacio
  • 69
  • 1
  • 3