3
votes

using react's ref today can be a little confusing. back to the days of class components, it was very clear in the docs.

we should use refs mostly for DOM elements :

https://reactjs.org/docs/refs-and-the-dom.html

But today we have hooks together with functional components. and we are using useRef hook.

also, this brings to us new patterns. using ref to contain callbacks, or any data we want to preserve (kind of state) but without the need to rerender. its a powerful API and also shows up in the docs:

https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

so now ref can be used for:

  1. store mutable data

  2. kind of memoization

but these docs contradict each other. and causing many mistakes, and conflicts in development teams.

the doc says 2 different things and its a problem.

so, what is the "right" thing to do in a scenario like that?


const MyComponent = (props) => {
   const [myMap1, _] = useState(new Map());    // 1.
   const myMap2 = useMemo(()=> new Map(), []); // 2.
   const myMap3 = useRef(new Map());           // 3.

   ...
};

2

2 Answers

1
votes

useMemo is a declarative performance optimization technique.

Declarative, as it relies on state or props dependencies from the surrounding scope belonging to a particular render and automatically (re)creates the memoized value. We don't have to tell useMemo to do that.

useRef is a mutable storage box for any values, e.g. value updates independent from current render scope.

useRef does not have any dependencies, so no value is automatically changed. You have to do that manually by writing ref.current = ... (DOM nodes are an exception). That is why you could see useRef as an escape hatch into the imperative world.

usage interchangeably
// instead of 
const val = useMemo(() => 42, [myDep]); // 42 stands for some complex calculation
// do this
const ref = useRef();
ref.current = 42 // assign 42 imperatively to ref somewhere in render
replaceuseRefuseState
const [ref] = useState({current:null});
ref.current = "hello";

Disadvantage: we are back in old jQuery age :-). Operations have to be set imperatively, meaning more manual work and possible mistakes. Immutable value manipulations are also much more predictable than mutations (incl. other benefits). That is, why nearly everything in React is immutable.

Instead, it makes sense to stay declarative when possible (React idiomatic) and use the intended, optimized tools for their specific case:

  • useState for immutable values in a declarative manner, triggers re-render
  • useMemo for declarative memoization of costly values derived from state and props
  • useRef for mutable values or DOM nodes - independent of render closure scope, no need for deps, doesn't trigger re-render, more manual imperative work
0
votes

The hook useRef can be used to store any value, for example, you have an object or an array or a map which you don't want to reinitialize for every single rerender you can use useRef hook.

 // this code will recreate the object in memory on each rerender
 const myObj = {foo: 'foo', bar: 'bar'}

 // here you have the same object in memory even after rerenders
 const refObj = useRef({foo: 'foo', bar: 'bar'})

similarly, useState hook can be used to store normal state variables.

useMemo is useful when some value depends on another variable in your code and you want to change the value only if the variable changes (useful when passing props as an object or passing a memoized object to the value prop passed to a context provider).