admin管理员组

文章数量:1024603

My Goal:

I'm trying to build a ponent that when you give it props.items and props.fadeEvery, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval.

Possible Issue:

I'm calling setIndex in the useEffect hook, but is that not good practice? How an I have it iterate through the array items infinitely?

TextFade.tsx

// Imports: Dependencies
import React, { useState, useEffect } from 'react';

// TypeScript Type: Props
interface Props {
  items: Array<string>,
  fadeEvery: number,
};

// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
  // React Hooks: State
  const [ index, setIndex ] = useState<number>(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      }
      else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};

// Exports
export default TextFade;

My Goal:

I'm trying to build a ponent that when you give it props.items and props.fadeEvery, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval.

Possible Issue:

I'm calling setIndex in the useEffect hook, but is that not good practice? How an I have it iterate through the array items infinitely?

TextFade.tsx

// Imports: Dependencies
import React, { useState, useEffect } from 'react';

// TypeScript Type: Props
interface Props {
  items: Array<string>,
  fadeEvery: number,
};

// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
  // React Hooks: State
  const [ index, setIndex ] = useState<number>(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      }
      else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};

// Exports
export default TextFade;
Share Improve this question asked Apr 7, 2021 at 20:49 jefelewisjefelewis 2,0592 gold badges34 silver badges68 bronze badges 3
  • index is in what's called a closure. Your use effect has been told to only render once [].. So you need to use the callback version of setIndex – Keith Commented Apr 7, 2021 at 20:54
  • 1 Just another heads up, if your using setInterval, it's most likely better to clearInterval not clearTimeout. MDN does say the ID's are shared, but for clarity its best to keep them in sync. – Keith Commented Apr 7, 2021 at 21:11
  • Does this answer your question? State not updating when using React state hook within setInterval – Yangshun Tay Commented Dec 17, 2022 at 0:42
Add a ment  | 

3 Answers 3

Reset to default 4

Your index values are taken from initital closure and it won't update unless useEffect callback is called again. You can instead use functional way to update state

useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array 
      setIndex(prevIdx => {
         if (prevIdx > props.items.length) {
            // Set Data
             return 0;
         }
         else {
           // Set Data
           return prevIdx + 1;
          }
      })
      
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

Below I've knocked up a snippet using the callback version of setState, this avoid the closure issue you get by using useEffect with []..

const {useState, useEffect} = React;


const TextFade = (props) => {
  // React Hooks: State
  const [ index, setIndex ] = useState(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      setIndex(index => 
        index + 1 >= props.items.length
          ? 0
          : index + 1);
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearInterval(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};



ReactDOM.render(<TextFade items={['one','two', 'three']} fadeEvery={1}/>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg./react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg./react-dom@17/umd/react-dom.development.js"></script>

<div id="mount"></div>

As @Keith said:

index is in what's called a closure. Your use effect has been told to only render once [].. So you need to use the callback version of setIndex

So, your useEffect hook will be:

useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      } else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, [index]);

Here is the working demo at CodeSandbox.

My Goal:

I'm trying to build a ponent that when you give it props.items and props.fadeEvery, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval.

Possible Issue:

I'm calling setIndex in the useEffect hook, but is that not good practice? How an I have it iterate through the array items infinitely?

TextFade.tsx

// Imports: Dependencies
import React, { useState, useEffect } from 'react';

// TypeScript Type: Props
interface Props {
  items: Array<string>,
  fadeEvery: number,
};

// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
  // React Hooks: State
  const [ index, setIndex ] = useState<number>(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      }
      else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};

// Exports
export default TextFade;

My Goal:

I'm trying to build a ponent that when you give it props.items and props.fadeEvery, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval.

Possible Issue:

I'm calling setIndex in the useEffect hook, but is that not good practice? How an I have it iterate through the array items infinitely?

TextFade.tsx

// Imports: Dependencies
import React, { useState, useEffect } from 'react';

// TypeScript Type: Props
interface Props {
  items: Array<string>,
  fadeEvery: number,
};

// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
  // React Hooks: State
  const [ index, setIndex ] = useState<number>(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      }
      else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};

// Exports
export default TextFade;
Share Improve this question asked Apr 7, 2021 at 20:49 jefelewisjefelewis 2,0592 gold badges34 silver badges68 bronze badges 3
  • index is in what's called a closure. Your use effect has been told to only render once [].. So you need to use the callback version of setIndex – Keith Commented Apr 7, 2021 at 20:54
  • 1 Just another heads up, if your using setInterval, it's most likely better to clearInterval not clearTimeout. MDN does say the ID's are shared, but for clarity its best to keep them in sync. – Keith Commented Apr 7, 2021 at 21:11
  • Does this answer your question? State not updating when using React state hook within setInterval – Yangshun Tay Commented Dec 17, 2022 at 0:42
Add a ment  | 

3 Answers 3

Reset to default 4

Your index values are taken from initital closure and it won't update unless useEffect callback is called again. You can instead use functional way to update state

useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array 
      setIndex(prevIdx => {
         if (prevIdx > props.items.length) {
            // Set Data
             return 0;
         }
         else {
           // Set Data
           return prevIdx + 1;
          }
      })
      
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, []);

Below I've knocked up a snippet using the callback version of setState, this avoid the closure issue you get by using useEffect with []..

const {useState, useEffect} = React;


const TextFade = (props) => {
  // React Hooks: State
  const [ index, setIndex ] = useState(0);

  // React Hooks: Lifecycle Methods
  useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      setIndex(index => 
        index + 1 >= props.items.length
          ? 0
          : index + 1);
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearInterval(timeoutID);
  }, []);

  return (
    <div id="text-fade-container">
      <p id="text-fade-text">{props.items[index]}</p>
    </div>
  );
};



ReactDOM.render(<TextFade items={['one','two', 'three']} fadeEvery={1}/>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg./react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg./react-dom@17/umd/react-dom.development.js"></script>

<div id="mount"></div>

As @Keith said:

index is in what's called a closure. Your use effect has been told to only render once [].. So you need to use the callback version of setIndex

So, your useEffect hook will be:

useEffect(() => {
    const timeoutID: number = window.setInterval(() => {
      // End Of Array
      if (index > props.items.length) {
        // Set Data
        setIndex(0);
      } else {
        // Set Data
        setIndex(index + 1);
      }
    }, props.fadeEvery * 1000);

    // Clear Timeout On Component Unmount
    return () => window.clearTimeout(timeoutID);
  }, [index]);

Here is the working demo at CodeSandbox.

本文标签: javascriptReact setInterval Not working after one intervalStack Overflow