• 1314 words

What's an object identity in JavaScript?

What's an object's identity? and what does "stable identity" mean in React? Let's dive a bit into it together!

Profile of a man's face in the dark
Photo by Ben Sweet / Unsplash

Hey there, today I’d like to tell you a bit about the concept of identity in OOP languages, especially in JavaScript, and what a “stable identity” means when talking about React.

You might have read about it here and there. One of the first times I’ve heard about it was many years ago in the old Angular documentation.
Nowadays the React documentation talks about it:

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

*I usually write what’s on my mind at a particular timeframe in this blog. I know I’ve previously talked about hooks in a way in which the content we’re going to see today was taken for granted back then, but still, it might be useful to someone, somehow.

What’s an identity?

The definition can be made quite simple. The identity of an object is what makes that object unique, that makes it recognizable from all the other objects.

The concept of identity, formulated in this way, is generally an OOP language concern, but in non-OOP languages such as C, the thing that resembles it the most is the idea of a memory address.
In fact, considering the high abstraction of languages like JavaScript, you usually don’t associate the identity of the values you manage with concepts like memory addresses, pointers, or whatever*.
The important thing is that you know that such a thing exists.

*Dan Abramov in its “Just JavaScript” course, in chapter 4, wrote about this too and he advises, for a variety of reasons, not to refer to it with a low-level language term.

Identities in JavaScript

Sometimes I stop thinking about my journey on mastering certain technologies and I’m greatly able to notice specific concepts that once learned, “clicked” something in my brain that made me gain a sense of greater awareness around it. Many years ago I’ve understood the concept of identity and it helped me so much that I’m sure that if you’re not still aware of it, it’ll help you a lot too.

In JavaScript when you’re comparing two non-primitive values such as literal objects, arrays, functions, and so on, you’re actually comparing their objects’ identities. Therefore if I write:

const a = {};
const b = {};

console.log(a === b);

this will print false exactly because equality checks on non-primitive values compare identities and not the content of those objects.

More or less similar things happen in other languages as I’ve mentioned earlier.
Where JS has these equality checks, in Python there is the `is` keyword, in C you have pointer comparisons, Rust, instead, takes a different approach at this forcing the dev to derive the PartialEq trait (thanks Fede 😁).

The stability of an identity

Returning to React, a characteristic that I love about it is that it’s “just” a rendering library built on top of JavaScript, and stability is indeed a key concept to understanding React hooks.

Something that’s stable, it’s something that doesn’t change.
But why on earth in React do identities change?
You’ve got to consider that whenever we define a function component, for React to be able to show that component, it needs to call that function. But if React keeps calling and calling our functions, how can these functions keep some sort of value between the various calls?

Imagine something like this:

const MyFun = () => {
  let state = "what's your ";

  return <button onClick={() => (state += "name")}>Change state</button>;
};

and imagine now that React calls this component for the first time. The user clicks the button, and then React calls it again because some parent re-rendered (this might be one of the many reasons).
At this point, the “state” variable’s value will again be re-initialized at its original value of course.

The solution? Other functions (called hooks) that when called return stable identities on which modifications are persistent throughout the lifetime of a component.

You might be asking at this point:

Why should I care about the fact that useState returns a value which has always the same identity? (as long as you don’t call the setters of course)

and now we’re finally approaching the last piece of this post.

Hooks’ dependencies

Most of the hooks provided by React have a dependency array and each time the value, or the identity, of one of its elements is changed the hook is re-triggered.

In this sentence, what does “changed” mean really?
It means that a strict equality comparison (===) between the value from the previous render and the actual one returns false.

Classical shenanigans in which react devs find themselves in due to this? Take a look at the following one:

const MyComponent = () => {
  const [state, setState] = useState({});

  useEffect(() => {
    setState({ ...state, newKey: "newValue" });
    // Or whatever update you want to perform based on the old state.
  }, [state]);
};

or also

const MyComponent = () => {
  const [localData, setLocalData] = useState();
  const fetchIt = () => localStorage.getItem("key");

  useEffect(() => {
    const data = fetchIt();
    const manipulatedData = JSON.parse(data);
    setLocalData(manipulatedData);
  }, [fetchIt]);
};

How do you solve these two is pretty simple and I’m going to show it to you just for completeness, but at the end of this article you’ll find a good resource with some cheatsheets on common pitfalls and how to avoid them.

Solutions

First things first, both are perfectly fine use cases that just need some semantic/syntactic adjustments to avoid infinite renderings of the component.

The first one is solvable using the setState function signature API that takes the previous state as a parameter so that the effect becomes:

useEffect(()=>{
    setState(prevState => {...prevState, newKey: 'newValue'});
}, []);

And that’s it.

While the second one is worth a brief analysis instead.
In it, we’re setting as a sole dependency of the useEffect the “fetchIt” function which is recreated at each render. Then we trigger a rerender of the component through the “setLocalData” call.
After this rerender, the entire function component is executed again, but this time we’ll have a different identity for the fetchIt function and the strict equality check of the useEffect on it will return false.
In this way, we’ve generated our own precious infinite cycle of renderings.

There are a ton of different ways to solve this generic problem, each of them specific to the situation.
The first obvious one, which is not always applicable of course, is to move the fetchIt definition outside of the component. In this way, the function’s identity will be stable since it won’t be recreated at each render.

The ideal one though, which is the one you very often need in your React code, is wrapping the function with the useCallback hook that returns a never-changing identity of the function as long as the values in its array of dependencies don’t change:

const MyComponent = () => {
  const [localData, setLocalData] = useState();
  const fetchIt = useCallback(() => localStorage.getItem("key"), []);

  useEffect(() => {
    const data = fetchIt();
    const manipulatedData = JSON.parse(data);
    setLocalData(manipulatedData);
  }, [fetchIt]);
};

But while the useCallback is the ideal solution, I challenge you to demonstrate how to keep an identity stable using each time a different hook from the core ones 😀 it can be a good exercise to master them all and to understand the subtle differences between them.

TIP: Keep also in mind that the set handlers of hooks like useState and useReducer (commonly referred to as setState and dispatch) are always stable and that’s the reason why it’s safe to omit them from hooks dependencies: because they never change.

Don’t ignore hooks lint warnings

I’ve worked on so many React codebases that I’m almost nauseated about the quantity of linting errors upon useEffects or useCallback with missing values in their dependency arrays.

If you’re questioning a linter or compiler works because of something you don’t fully understand, you’re always wrong.

Other resources on hooks

With this post, I wanted to tackle the “hooks” argument in a different, unusual manner that doesn’t go into details about hooks, but mostly about terminology and programming languages. If you’re seeking a “guide” on how to use hooks, instead, you can check this.

Keep rocking 😃👋🏻