mahooresorkh
mahooresorkh

Reputation: 1444

State in useState does not change

I have a functional component

import React, { useState } from 'react'

export default function AboutUs() {
const [counter,setCounter] = useState(0);

const clicked = () => {
    setCounter(counter+1);
}

return (
    <div>
        <p>you clicked {counter} times</p>
        <button onClick={clicked}>Click</button>        
    </div>
)
}

there is nothing wrong with that and it works just fine. But when I remove onClick in button element and add the event listener like:

import React, { useEffect, useState } from 'react'

export default function AboutUs() {
const [counter,setCounter] = useState(0);

useEffect(()=>{
    document.getElementById('test').addEventListener('click',clicked);
},[])

const clicked = () => {
    setCounter(counter+1);
}

return (
    <div>
        <p>you clicked {counter} times</p>
        <button id="test">Click</button>        
    </div>
)
}

after one click counter value will be 1 and then it stops! the counter does not change by setCounter anymore! what is wrong with that? I would appreciate your help.

Upvotes: 1

Views: 205

Answers (2)

Valentin
Valentin

Reputation: 11792

There are 2 problems here:

  1. useEffect dependency is not specified
  2. useEffect does not have a cleanup handler Updating your code would give something like this:
useEffect(() => {
  const element = document.getElementById('test');
  element.addEventListener('click', clicked);
  return () => { element.removeEventListener('click', clicked) };
}, [clicked]);

But an issue remains: the clicked function reference changes at each render which means that the useEffect will remove/add the listener at every render. To avoid this, you should wrap your clicked handler in a useCallback with proper dependencies like this:

const clicked = useCallback(() => {
    setCounter(counter + 1);
}, [counter]);

Upvotes: 1

Quentin
Quentin

Reputation: 943097

I have a functional component

You do not.

Your component tracks state which violates the principle of functional programming that says functions should have no side effects.

You have a function component.


the counter does not change by setCounter anymore!

The event handler is attached in a useEffect hook.

That hook gets called when the component first mounts and when any of the dependencies change.

The dependencies list is [] so the dependancies will never change.

Each time it is called (which we've just established is only once) it will create a function that closes over the counter constant (initially set to 0)

Each time the element is clicked on you'll get setCounter(counter+1); where counter is always 0 because that is the value of the constant that was closed over.

(Even though changing the state triggers a re-render which creates a different constant with the same name).


To do this using that approach you would generally do something like this:

useEffect(()=>{
    document.getElementById('test').addEventListener('click',clicked);
    return () => document.getElementById('test').removeEventListener('click', 'clicked');
},[clicked])

So each time the function changes it cleans up the previous event listener and adds the new one (which has closed over the new constant)


But direct DOM manipulation is fragile and throws away the main benefits of React. So don't do that. Use the onClick={click} approach, that is how React is designed to work.

Upvotes: 7

Related Questions