HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactCritical

React hooks - right way to clear timeouts and intervals

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
hookswayclearandintervalsreacttimeoutsright

Problem

I don't understand why is when I use setTimeout function my react component start to infinite console.log. Everything is working, but PC start to lag as hell.
Some people saying that function in timeout changing my state and that rerender component, that sets new timer and so on. Now I need to understand how to clear it's right.

export default function Loading() {
  // if data fetching is slow, after 1 sec i will show some loading animation
  const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)

  console.log('this message will render  every second')
  return 1
}


Clear in different version of code not helping to:

const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)
  useEffect(
    () => {
      return () => {
        clearTimeout(timer1)
      }
    },
    [showLoading]
  )

Solution

Defined return () => { /code/ } function inside useEffect runs every time useEffect runs (except first render on component mount) and on component unmount (if you don't display component any more).
This is a working way to use and clear timeouts or intervals:

Sandbox example.

import { useState, useEffect } from "react";

const delay = 5;

export default function App() {
  const [show, setShow] = useState(false);

  useEffect(
    () => {
      let timer1 = setTimeout(() => setShow(true), delay * 1000);

      // this will clear Timeout
      // when component unmount like in willComponentUnmount
      // and show will not change to true
      return () => {
        clearTimeout(timer1);
      };
    },
    // useEffect will run only one time with empty []
    // if you pass a value to array,
    // like this - [data]
    // than clearTimeout will run every time
    // this value changes (useEffect re-run)
    []
  );

  return show ? (
    show is true, {delay}seconds passed
  ) : (
    show is false, wait {delay}seconds
  );
}


If you need to clear timeouts or intervals in another component:

Sandbox example.

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

const delay = 1;

export default function App() {
  const [counter, setCounter] = useState(0);
  const timer = useRef(null); // we can save timer in useRef and pass it to child

  useEffect(() => {
    // useRef value stored in .current property
    timer.current = setInterval(() => setCounter((v) => v + 1), delay * 1000);

    // clear on component unmount
    return () => {
      clearInterval(timer.current);
    };
  }, []);

  return (
    
      Interval is working, counter is: {counter}
      
    
  );
}

function Child({ counter, currentTimer }) {
  // this will clearInterval in parent component after counter gets to 5
  useEffect(() => {
    if (counter < 5) return;

    clearInterval(currentTimer);
  }, [counter, currentTimer]);

  return null;
}


Article from Dan Abramov.
If you need to clear timeouts or intervals in useCallback:

function TimeoutComponent() {
  const [message, setMessage] = useState('');
  const timeoutRef = React.useRef(null);

  const startTimeout = useCallback(() => {
    // Clear any existing timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    // Set new timeout
    timeoutRef.current = setTimeout(() => {
      setMessage('Timeout completed!');
    }, 2000);
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);

  return (
    
      Start Timeout
      {message}
    
  );
}

Code Snippets

import { useState, useEffect } from "react";

const delay = 5;

export default function App() {
  const [show, setShow] = useState(false);

  useEffect(
    () => {
      let timer1 = setTimeout(() => setShow(true), delay * 1000);

      // this will clear Timeout
      // when component unmount like in willComponentUnmount
      // and show will not change to true
      return () => {
        clearTimeout(timer1);
      };
    },
    // useEffect will run only one time with empty []
    // if you pass a value to array,
    // like this - [data]
    // than clearTimeout will run every time
    // this value changes (useEffect re-run)
    []
  );

  return show ? (
    <div>show is true, {delay}seconds passed</div>
  ) : (
    <div>show is false, wait {delay}seconds</div>
  );
}
import { useState, useEffect, useRef } from "react";

const delay = 1;

export default function App() {
  const [counter, setCounter] = useState(0);
  const timer = useRef(null); // we can save timer in useRef and pass it to child

  useEffect(() => {
    // useRef value stored in .current property
    timer.current = setInterval(() => setCounter((v) => v + 1), delay * 1000);

    // clear on component unmount
    return () => {
      clearInterval(timer.current);
    };
  }, []);

  return (
    <div>
      <div>Interval is working, counter is: {counter}</div>
      <Child counter={counter} currentTimer={timer.current} />
    </div>
  );
}

function Child({ counter, currentTimer }) {
  // this will clearInterval in parent component after counter gets to 5
  useEffect(() => {
    if (counter < 5) return;

    clearInterval(currentTimer);
  }, [counter, currentTimer]);

  return null;
}
function TimeoutComponent() {
  const [message, setMessage] = useState('');
  const timeoutRef = React.useRef(null);

  const startTimeout = useCallback(() => {
    // Clear any existing timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    // Set new timeout
    timeoutRef.current = setTimeout(() => {
      setMessage('Timeout completed!');
    }, 2000);
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);

  return (
    <div>
      <button onClick={startTimeout}>Start Timeout</button>
      <p>{message}</p>
    </div>
  );
}

Context

Stack Overflow Q#53090432, score: 410

Revisions (0)

No revisions yet.