Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

214
Vistas
Matching setState interval with css animation

I am rotating the items in an array of images inside an interval of 5 seconds. Then I have a css animation with styled components that eases in the gallery images with a fade that occurs at the same time interval.

const fadeIn = keyframes`
  5%, 95% { opacity: 1 }
  100% { opacity: 0 }
`
export const Gallery = styled.div<{ lapse: number }>`
  position: relative;
  margin: 0 auto;
  max-width: 1064px;
  opacity: 0;
  animation: ${fadeIn} ease-in-out ${({ lapse }) => `${lapse}s`} infinite;
`

The problem is that when I change the state even thou at first it seems in sync, eventually the setState takes a bit longer

import React, { useState, useEffect } from 'react'
const [images, setImages] = useState<string[]>([])

// Time in seconds for each image swap, fading in between
const lapse = 5
...

useEffect(() => {
  // Clone the images array
  const imgs = [...images]
        
  // Time interval same as css animation to fade in
  const interval = setInterval(() => {
    // Take the first element and put it at the end
    imgs.push(...imgs.splice(0, 1))
    // Update the state, this seems to desync as time passes
    setImages(imgs)
  }, lapse * 1000)

  return () => clearInterval(interval)
}, [images])
        

return (
  <Gallery lapse={lapse}>
    <Portal>
      <img src={imgs/${images[0]}`}
    </Portal>
    <Thumbnails>
      <Thumbwrapper> 
        <img src={imgs/${images[1]}`}
      </Thumbwrapper>
      <Thumbwrapper> 
        <img src={imgs/${images[2]}`}
      </Thumbwrapper>
     </Thumbnails>
  </Gallery>
)

Is there a way I can make sure the swapping happends smoothly?

enter image description here

about 4 years ago · Juan Pablo Isaza
3 Respuestas
Responde la pregunta

0

Make the component class based,

constructor(props) {
    super(props);
    this.state = {
        interval: null,
        images: ["1.jpg", "2.jpg", "3.jpg"],
        timeChanged: null
    };
}

// setInterval when component is mounted
componentDidMount() {
    var interval = setInterval(()=> {
        // use callback argument for setState 
        // when new value (images) depends on old value
        this.setState((state)=>({
            timeChanged: (new Date()).toString(),
            images: state.images.slice(1).concat(state.images[0]) 
            // add 1st img at the end
        });
    }, 3000);
    this.setState({interval: interval});
}

// clearInterval before component is unmounted
componentWillUnmount() {
    clearInterval(this.state.interval);
}

When the state is updated using its old value the callback argument should be used.
(See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)

slice returns a portion of an array.

Also see how to update state arrays in React.

If you still wish to use hooks. Just call setImages with (images)=>images.slice(1).concat(images[0]).
I'm not sure where the clearTimeout should be, when using hooks.

about 4 years ago · Juan Pablo Isaza Denunciar

0

It would be great if we can have example in codesandbox. Also I would like to ask you, maybe I am missing something, but how do you determine when to stop using effect, since it depends on image, which is, I assume, part of the useSate hook, and in that same effect you are updating that same image, which will lead to another effect usage(leading to infinity loop)?

UPDATED ANSWER

I made similar example on codesandbox to look it on my own. It looks to me that main problem here is sync between applied CSS and actual update in your react component. When using setInterval with 5000ms it does not mean that it will be triggered right after 5000ms, but CSS animation on the other side is always right on time, so it stuck when two of those become unsynced. It looks like the only way to solve this is to recreate img elements on each reorder, by introducing timestamp state and use it as part of the each img key in order to force img recreation on each update. Also you would need to change animation in simpler way.

CSS would besomething like this:

  @keyframes fadeIn {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

.image {
  position: relative;
  margin: 0 auto;
  max-width: 1064px;
  animation: fadeIn 3s;
  opacity: 1;
}

And functional component would be something like this(hook part only):

      const [images, setImages] = useState(["1.jpg", "2.jpg", "3.jpg"]);
      const [dateTimeWhenChanged, setDatetimeWhenChanged] = useState("");
    
      useEffect(() => {
        const imgs = [...images];
    
        // Time interval same as css animation to fade in
        const interval = setInterval(() => {
          // Take the first element and but it to the end
          imgs.push(...imgs.splice(0, 1));
          // Update the state, this seems to desync as time passes
          setImages(imgs);
          setDatetimeWhenChanged(new Date().toString());
        }, 5 * 1000);
        
         return (
    <div>
      {images.map((img, ind) => (
        <img
          key={`${ind}-${dateTimeWhenChanged}`}
          alt={img}
          src={`images/${img}`}
          className="imgg"
        />
      ))}
    </div>
  );
about 4 years ago · Juan Pablo Isaza Denunciar

0

I came to the conclusion that having two 'clocks' to keep the time, one being react setState and the other css animation, was at the core of the problem.

So I am now keeping a single interval with a setTimeout to make sure it goes in order then substract the time taken on the timeouts from the interval and toggle the class for css

export const Gallery = styled.div<{ fadeIn: boolean }>`
  position: relative;
  margin: 0 auto;
  max-width: 1064px;
  transition: opacity 0.3s;
  opacity: ${({ fadeIn }) => (fadeIn ? 1 : 0)};
`
import React, { useState, useEffect } from 'react'
const [images, setImages] = useState<string[]>([])
const [fadeIn, setFadeIn] = useState<boolean>(true)

useEffect(() => {
  let interval: ReturnType<typeof setInterval>
  if (images.length) {

    interval = setInterval(() => {
      setFadeIn(false)

      setTimeout(() => {
        setImages(images => images.slice(1).concat(images[0]) )
        setFadeIn(true)
      }, 400)
    }, (lapse - 0.4) * 1000)
  }
  return () => clearInterval(interval)
}, [images])

const getImage = (i: number) =>  <img src={imgs/${images[i]}`} />

<Gallery fadeIn={fadeIn}>
  <Portal>{getImage(0)}</Portal>
  <Thumbnails>
    <Thumbwrapper>{getImage(1)}</Thumbwrapper>
    <Thumbwrapper>{getImage(2)}</Thumbwrapper>
  </Thumbnails>
</Gallery>

I did however used @Nice Books better setState aproach

about 4 years ago · Juan Pablo Isaza Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda