2

I am changing text between two values using React useState and setInterval and it works fine.

function Home() {
  const [role, setRole] = useState("Text A");

  const inter = setInterval(() => {
    changeHome();
  }, 5000);

  function changeHome() {
    role === "Text A" ? setRole("Text B") : setRole("Text A");

    clearInterval(inter);
  }

  useEffect(() => {
    //className&&setClassName("fade")
  }, [role]);

  return (
    <div id="home">
      <div className="text">
        <span>Hello!</span>
        <h1>
          I'm <span>Karim Isaac</span>
        </h1>
        <h2 className="fade">{role}</h2>

        
      </div>
    </div>
  );
}

export default Home;

I am trying to add animation to the text when it changes using CSS:

@keyframes fade-down {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

.fade {
  animation: fade-down 2.5s infinite alternate;
}

It works but the timing is not perfectly well. I think that's because there are two timers; setinterval and animation timer.

2
  • Please give more insight regarding the issue: What is going wrong ? What is expected ? Commented Jun 20, 2024 at 13:10
  • Thanks for your reply. When the opacity is 1 for example the state switches between the two texts. I want to switch between texts when opacity 0. Commented Jun 20, 2024 at 15:02

1 Answer 1

0

Here is a useInterval hook that uses a reference i.e. useRef, and returns a callback to stop the interval.

The reference will help you avoid running into an endless state update loop.

Note: I also encapsulated the role text values into an array.

const { useCallback, useEffect, useRef, useState } = React;

const useInterval = (callback, delay) => {
  const intervalIdRef = useRef(null);
  const savedCallbackRef = useRef(callback);

  // Remember the latest callback if it changes
  useEffect(() => (savedCallbackRef.current = callback), [callback]);

  // Set up the interval
  useEffect(() => {
    const tick = () => (savedCallbackRef.current());

    if (delay !== null) {
      intervalIdRef.current = setInterval(tick, delay);
      console.log(`Started interval: ${intervalIdRef.current}`);
      
      return () => {
        if (intervalIdRef.current !== null) {
          console.log(`Cleaning up interval: ${intervalIdRef.current}`);
          clearInterval(intervalIdRef.current);
        }
      };
    }
  }, [delay]);

  const clear = useCallback(() => {
    if (intervalIdRef.current !== null) {
      console.log(`Stopping interval: ${intervalIdRef.current}`);
      clearInterval(intervalIdRef.current);
      intervalIdRef.current = null;
    }
  }, []);

  return clear;
};

const roles = ["Text A", "Text B"];

const Home = () => {
  const [role, setRole] = useState(roles[0]);

  const toggleRole = useCallback(() => {
    setRole((currentRole) => (currentRole === roles[0] ? roles[1] : roles[0]));
  }, []);

  const stopInterval = useInterval(toggleRole, 5000);

  return (
    <div id="home">
      <div className="text">
        <span>Hello!</span>
        <h1>I'm <span>Karim Isaac</span></h1>
        <h2 className="fade">{role}</h2>

        <div className="Stack">
          <button type="button">Hire Me</button>
          <button type="button">My works</button>
          <button type="button" onClick={stopInterval}>STOP</button>
        </div>
      </div>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById("root")).render(<Home />);
@keyframes fade-down {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

.fade {
  animation: fade-down 2.5s infinite alternate;
}

.Stack {
  display: flex;
  flex-direction: row;
  gap: 0.5rem;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Here is a version without the hook:

const { useCallback, useEffect, useState } = React;

const roles = ["Text A", "Text B"];

const Home = () => {
  const [role, setRole] = useState(roles[0]);
  const [intervalId, setIntervalId] = useState(null);

  const toggleRole = useCallback(() => {
    setRole((currentRole) => (currentRole === roles[0] ? roles[1] : roles[0]));
  }, []);

  const stopInterval = useCallback(() => {
    if (intervalId !== null) {
      console.log(`Stopping interval: ${intervalId}`);
      clearInterval(intervalId);
      setIntervalId(null);
    }
  }, [intervalId]);

  useEffect(() => {
    stopInterval();  // Ensure any previous interval is cleared before starting a new one
    const newId = setInterval(toggleRole, 5000);
    console.log(`Started interval: ${newId}`);
    setIntervalId(newId);

    return () => {
      console.log(`Cleaning up interval: ${newId}`);
      clearInterval(newId);
    };
  }, [toggleRole]);

  return (
    <div id="home">
      <div className="text">
        <span>Hello!</span>
        <h1>I'm <span>Karim Isaac</span></h1>
        <h2 className="fade">{role}</h2>

        <div className="Stack">
          <button type="button">Hire Me</button>
          <button type="button">My works</button>
          <button type="button" onClick={stopInterval}>STOP</button>
        </div>
      </div>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById("root")).render(<Home />);
@keyframes fade-down {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

.fade {
  animation: fade-down 2.5s infinite alternate;
}

.Stack {
  display: flex;
  flex-direction: row;
  gap: 0.5rem;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your reply. If you waited sometime, the same issue would occur. For the opacity is 1 for example and switches between the two texts.
The problem is SetInterval doesn't wait exactly for 5s, it waits for 5s and waits for the microtask queue to clear up. So it is garanteed to wait at least for 5s but it might last longer, that's what is creating this delay which stacks up with time. The right approch would be to have 2 different animation and trigger them by hand each time the text changes
Mb, SetInterval creates a task, not a microtask, but the logic is the same

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.