Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

128
Visualizações
Same logic but different behaviour in 'class' and in 'functional component'

Attempted to translate an example code from class to functional component and faced the problem.

the target file is in components/Wheel/index.js

Key function that causes problem

  const selectItem = () => {
   
   if (selectedItem === null) {
   
     const selectedItem = Math.floor(Math.random() * items.length);
     console.log(selectedItem);
     setSelectedItem(selectedItem);
   } else {
     
     setSelectedItem(null);
    let t= setTimeout(() => {
   
       selectItem()
     }, 500);
   clearTimeout(t);
   }
 };

First time is normal, from second time onward, 2 clicks are needed for the wheel to spin. I had to add clearTimeout() or infinite loop is resulted, but the same does not happen in the original.

Original working example in class

Codesandbox Demo

My version in functional component.

MyVersion

Thank you.

about 4 years ago · Juan Pablo Isaza
1 Respostas
Responde à pergunta

0

What an excellent nuance of hooks you've discovered. When you call selectItem in the timeout, the value of selectedItem that is captured in lexical scope is the last value (not null).

There's two answers, a simple answer and a better working answer.

The simple answer is you can accomplish it be simply separating the functions: https://codesandbox.io/s/spinning-wheel-game-forked-cecpi

It looks like this:


  const doSelect = () => {
    setSelectedItem(Math.floor(Math.random() * items.length));
  };

  const selectItem = () => {
    if (selectedItem === null) {
      doSelect();
    } else {
      setSelectedItem(null);
      setTimeout(doSelect, 500);
    }
  };

Now, read on if you dare.

The complicated answer fixes the solution for the problem if items.length may change in between the time a timer is set up and it is fired:

https://codesandbox.io/s/spinning-wheel-game-forked-wmeku

Rerendering (i.e. setting state) in a timeout causes complexity - if the component re-rendered in between the timeout, then your callback could've captured "stale" props/state. So there's a lot going on here. I'll try and describe it as best I can:

  const [selectedItem, setSelectedItem] = useState(null);

  // we're going to use a ref to store our timer
  const timer = useRef();

  const { items } = props;

  // this is just the callback that performs a random select
  // you can see it is dependent on items.length from props
  const doSelect = useCallback(() => {
    setSelectedItem(Math.floor(Math.random() * items.length));
  }, [items.length]);


  // this is the callback to setup a timeout that we do
  // after the user has clicked a "second" time.
  // it is dependent on doSelect
  const doTimeout = useCallback(() => {
    timer.current = setTimeout(() => {
      doSelect();
      timer.current = null;
    }, 500);
  }, [doSelect]);

  // Here's the tricky thing: if items.length changes in between 
  // the time we rerender and our timer fires, then the timer callback will have 
  // captured a stale value for items.length.
  // The way we fix this is by using this effect.
  // If items.length changes and there is a timer in progress we need to:
  // 1. clear it
  // 2. run it again
  //
  // In a perfect world we'd be capturing the amount of time remaining in the 
  // timer and fire it exactly (which requires another ref)
  // feel free to try and implement that!
  useEffect(() => {
    if (!timer.current) return;
    clearTimeout(timer.current);
    doTimeout();
    // it's safe to ignore this warning because
    // we know exactly what the dependencies are here
  }, [items.length, doTimeout]);

  const selectItem = () => {
    if (selectedItem === null) {
      doSelect();
    } else {
      setSelectedItem(null);
      doTimeout();
    }
  };
about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda