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

108
Visualizações
Is it a common practice in React to keep the same value in state and ref?

My React app uses setTimeout() and setInterval(). Inside them, I need to access the state value. As we know, closures are bound to their context once created, so using state values in setTimeout() / setInterval() won't use the newest value.

Let's keep things simple and say my component is defined as such:

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

const Foo = () => {
    const [number, setNumber] = useState(0);
    const numberRef = useRef(number);

    // Is this common? Any pitfalls? Can it be done better?
    numberRef.current = number;

    useEffect(
        () => setInterval(
            () => {
                if (numberRef.current % 2 === 0) {
                    console.log('Yay!');
                }
            },
            1000
        ),
        []
    );

    return (
        <>
            <button type="button" onClick={() => setNumber(n => n + 1)}>
                Add one
            </button>
            <div>Number: {number}</div>
        </>
    );
};

In total I came up with 3 ideas how to achieve this, is any of them a recognized pattern?

  1. Assigning state value to ref on every render, just like above:

    numberRef.current = number;
    

    The benefit is very simplistic code.

  2. Using useEffect() to register changes of number:

    useEffect(
        () => numberRef.current = number,
        [number]
    );
    

    This one looks more React-ish, but is it really necessary? Doesn't it actually downgrade the performance when a simple assignment from point #1 could be used?

  3. Using custom setter:

    const [number, setNumberState] = useState(0);
    const numberRef = useRef(number);
    
    const setNumber = value => {
        setNumberState(value);
        numberRef.current = value;
    };
    

Is having the same value in the state and the ref a common pattern with React? And is any of these 3 ways more popular than others for any reason? What are the alternatives?

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

0

2021-10-17 EDIT:

Since this looks like a common scenario I wanted to wrap this whole logic into an intuitive

useInterval(
  () => console.log(`latest number value is: ${number}`), 
  1000
)

where useInterval parameter can always "access" latest state.

After playing around for a bit in a CodeSandbox I've come to the realization that there is no way someone else hasn't already thought about a solution for this.

Lo and behold, the man himself, Dan Abramov has a blog post with a precise solution for our question https://overreacted.io/making-setinterval-declarative-with-react-hooks/

I highly recommend reading the full blog since it describes a general issue with the mismatch between declarative React programming and imperative APIs. Dan also explains his process (step by step) of developing a full solution with an ability to change interval delay when needed.

Here (CodeSandbox) you can test it in your particular case.


ORIGINAL answer:

1.

numberRef.current = number;

I would avoid this since we generally want to do state/ref updates in the useEffect instead of the render method.

In this particular case, it doesn't have much impact, however, if you were to add another state and modify it -> a render cycle would be triggered -> this code would also run and assign a value for no reason (number value wouldn't change).

2.

useEffect(
    () => numberRef.current = number,
    [number]
);

IMHO, this is the best way out of all the 3 ways you provided. This is a clean/declarative way of "syncing" managed state to the mutable ref object.

3.

const [number, setNumberState] = useState(0);
const numberRef = useRef(number);

const setNumber = value => {
    setNumberState(value);
    numberRef.current = value;
};

In my opinion, this is not ideal. Other developers are used to React API and might not see your custom setter and instead use a default setNumberState when adding more logic expecting it to be used as a "source of truth" -> setInterval will not get the latest data.

about 4 years ago · Juan Pablo Isaza Relatório

0

You have simply forgotten to clear interval. You have to clear the interval on rendering.

  useEffect(() => {
    const id = setInterval(() => {
      if (numberRef.current % 2 === 0) {
        console.log("Yay!");
      }
    }, 1000);
    return () => clearInterval(id);
  }, []);

If you won't clear, this will keep creating a new setInterval with every click. That can lead to unwanted behaviour.

Simplified code:

const Foo = () => {
  const [number, setNumber] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      if (number % 2 === 0) {
        console.log("Yay!");
      }
    }, 1000);
    return () => clearInterval(id);
  }, [number]);

  return (
    <div>
      <button type="button" onClick={() => setNumber(number + 1)}>
        Add one
      </button>
      <div>Number: {number}</div>
    </div>
  );
};
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