Estoy rotando los elementos en una matriz de imágenes dentro de un intervalo de 5 segundos. Luego tengo una animación css con componentes con estilo que facilita las imágenes de la galería con un desvanecimiento que ocurre en el mismo intervalo de tiempo.
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; ` El problema es que cuando cambio el state , aunque al principio parece estar sincronizado, finalmente el setState tarda un poco más.
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> )¿Hay alguna manera de asegurarme de que el intercambio se realice sin problemas?
Haga que la clase de componente se base,
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); } Cuando el estado se actualiza usando su valor anterior, se debe usar el argumento de devolución de llamada.
(Ver https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous )
slice devuelve una parte de una matriz.
También vea cómo actualizar matrices de estado en React .
Si aún desea utilizar ganchos. Simplemente llame a setImages con (images)=>images.slice(1).concat(images[0]) .
No estoy seguro de dónde debería estar clearTimeout cuando se usan ganchos.
Sería genial si pudiéramos tener un ejemplo en codesandbox. También me gustaría preguntarte, tal vez me estoy perdiendo algo, pero ¿cómo determinas cuándo dejar de usar el efecto, ya que depende de la image , que es, supongo, parte del useSate gancho, y en ese mismo efecto estás actualizando esa misma image , lo que conducirá a otro uso de efectos (que conduce a un bucle infinito)?
RESPUESTA ACTUALIZADA
Hice un ejemplo similar en codesandbox para verlo por mi cuenta. Me parece que el problema principal aquí es la sincronización entre el CSS aplicado y la actualización real en su componente de reacción. Cuando se usa setInterval con 5000 ms, no significa que se activará justo después de 5000 ms, pero la animación CSS en el otro lado siempre llega a tiempo, por lo que se atasca cuando dos de ellos se desincronizan. Parece que la única forma de resolver esto es recrear elementos img en cada reorden, introduciendo el estado de marca de tiempo y usándolo como parte de cada clave img para forzar la recreación de img en cada actualización. También necesitaría cambiar la animación de una manera más simple.
CSS sería algo como esto:
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .image { position: relative; margin: 0 auto; max-width: 1064px; animation: fadeIn 3s; opacity: 1; }Y el componente funcional sería algo como esto (solo la parte del gancho):
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> );Llegué a la conclusión de que tener dos 'relojes' para mantener el tiempo, uno reaccionando setState y el otro css animation, era el núcleo del problema.
Así que ahora mantengo un solo intervalo con un setTimeout para asegurarme de que vaya en orden, luego resto el tiempo que se toma en los tiempos de espera del intervalo y cambio la clase para 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> Sin embargo, usé @Nice Books mejor setState