Search code examples
javascriptreact-hooksevent-listener

State is not being updated in useCallback


FINAL EDIT: I solved it, check my answer below

EDIT: So, the problem boils down to this. How can i change the value of a state from inside an eventListener? I have tried the things below, as well as the advice of the people below. I can change it no problem from outside of an event listener but for this usecase it is quite important that it changes through an event

I have a boolean state variable whose job is to re-render an element.

In the use effect hook it works as expected, whenever one of the deps changes, it flips and re-renders the component.

The thing is, i need it to be fired with an event listener and it just does not.

<EDIT>: Sorry for the confusion, i am still learning. I changed the code a bit.

const transition = () => {
    console.log("running callback");
    const content = document.getElementById("content");
    const greeting = document.querySelector(".fadeInAndOut");
    greeting?.addEventListener(
      "animationend",
      () => {
        console.log("event fired");
        greeting?.classList.remove("fadeInAndOut");
        greeting?.setAttribute("style", "opacity: 0");
        content?.classList.add("fadeIn");
        console.log(`1 ${reset}`);
        setReset(!reset);
        console.log(`2 ${reset}`);
        cleanUp();
        greeting?.removeEventListener("animationend", () => {
          console.log("removed2");
        });
      },
      { once: true }
    );
  };

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

This is the effect that people have been asking to see. It flips the reset value when needed but the flipping in the event listener will not work even now

  useEffect(() => {
    setReset(!reset);
  }, [page, lang]);

</EDIT>

Here is the code:

  const transition = useCallback(() => {
    console.log("running callback");
    const content = document.getElementById("content");
    const greeting = document.querySelector(".fadeInAndOut");
    greeting?.addEventListener(
      "animationend",
      () => {
        console.log("event fired");
        greeting?.classList.remove("fadeInAndOut");
        greeting?.setAttribute("style", "opacity: 0");
        content?.classList.add("fadeIn");
        console.log(`1 ${reset}`);
        setReset(!reset);
        console.log(`2 ${reset}`);
        cleanUp();
      },
      { once: true }
    );
  }, []);

When i console log it in the useEffect case, i get true,false,true etc... console.log from the useCallback always returns the same value

I need this to only happen on first page load, hence the useCallback with no deps.

EDIT: changing the use callback to include "reset" in the deps does not solve the issue and also causes it to fire again. changing the setReset to

setReset(reset => !reset)

also does not change the state

Bonus question: I know react will fire the useCallback twice when developing but even though the event listener has the "once: true" option it gets added and therefore fired twice. Is something wrong with my declaration of it?


Solution

  • So, i am not very smart. As the good people commented i never needed to use the useCallback hook. Now,i cannot get an event listener added to the element to fire a setState. So what i did was use this:

    return (
        <>
          <Loading lang={lang} onAnimationEnd={handleAnimationEnd}></Loading>
          <Container style={{ opacity: 0 }} id="content" onAnimationEnd={cleanUp}>
    ...Rest of the html
    

    and here are the functions

    const cleanUp = () => {
        const target = document.getElementById("content");
    
        target?.setAttribute("style", "opacity: 1");
        target?.classList.remove("fadeIn");
        target?.removeEventListener("animationed", cleanUp);
      };
    
      const handleAnimationEnd = () => {
        const content = document.getElementById("content");
        const greeting = document.querySelector(".fadeInAndOut");
    
        console.log("event fired");
        greeting?.setAttribute("style", "display: none");
        greeting?.classList.remove("fadeInAndOut");
        content?.classList.add("fadeIn");
        setReset(!reset);
        greeting?.removeEventListener("animationend", handleAnimationEnd);
      };