The React Hook Chronicles: Decoding the Mysteries of JSON.stringify

Deepak Surya
Courtly & Intrepid
Published in
4 min readJan 10, 2024
Photo by Patrick Tomasso on Unsplash

Ever found yourself in a bit of a pickle with React’s useEffect, useMemo, or useCallback hooks? You know, when you're trying to track changes in your state, but it's like herding cats because React's just looking at the surface?

Well, you might have heard about a nifty trick using JSON.stringify to turn those pesky objects or arrays into strings that React can easily keep an eye on. But before you jump into this seemingly simple solution, let's spill the tea on what's really going on under the hood. It's not all smooth sailing, and understanding the ins and outs can save you a headache down the line. So, grab a cuppa, get comfy, and let's unravel the mystery of JSON.stringify in React's hooks – it's a game-changer, but with a few twists you ought to know!

Why You’d Do It

React’s hooks get a bit miffed if you chuck in objects or arrays directly into the deps array ’cause they only peek at the surface.

Primitive values such as strings, numbers, and boolean are a doodle to handle. These hooks will run every time the value changes. But for reference types (objects, arrays, or functions), React only does a shallow comparison, checking if the reference has changed, not the content. Therefore the hooks will run only when the actual reference to the object changes. And this reference renews every render.

JSON.stringify turns all that malarkey inside your objects or arrays into a string, and strings are dead easy for React to examine for changes.

But Wait, It Ain’t All Rosy

However, before you start celebrating, it’s not all a bed of roses. Using JSON.stringify in React hooks comes with its own set of challenges and quirks. Let's dive into why this approach might be more of a sticky wicket than a walk in the park.

  1. Performance Goes Down the Loo: Using JSON.stringify is like trying to run with lead boots on – it’s heavy, especially with big, bulky objects. It can slow your app down, and no one wants that.
  2. Unnecessary Rerenders: If your object looks different but ain’t really changed inside, React throws a fit and rerenders when it doesn’t need to because JSON.stringify creates a new string on every render. Bit of a time waster, that.
  3. Circular References: JSON.stringify will throw an error if your object is like a dog chasing its tail (you know, circular references). It just can’t handle it.
  4. Mixing Things Up: The order of the properties in objects ain’t set in stone. When you combine integers, strings and Symbols, the integers are always in the front row followed by strings and Symbols. We can control the order of the string and Symbol properties because they’re chronological.
const obj = {
'2': 'integer: 2',
'foo': 'string: foo',
'01': 'string: 01',
1: 'integer: 1',
[Symbol('first')]: 'symbol: first'
};

obj['0'] = '0';
obj[Symbol('last')] = 'symbol: last';
obj['veryLast'] = 'string: very last';
console.log(Reflect.ownKeys(obj));
// [ "0", "1", "2", "foo", "01", "veryLast", Symbol(first), Symbol(last) ]
// -> 1. integers in numeric order
// -> 2. strings in chronological order
// -> 3. Symbols in chronological order
  1. Some Things Just Don’t Translate: Certain bits and bobs (like functions or undefined or Symbol) turn into a pumpkin when hit with JSON.stringify. They just vanish, poof!

Better Ways to Skin the Cat

  1. Memoization of Objects: Instead of turning everything into strings, just keep your objects in check with useMemo. Only change ‘em when you need to.
  2. Be Your Own Detective: If things get complex, roll up your sleeves and write a custom function to suss out when things really change.
  3. Keep It Simple, Stupid: Try to keep your state and props easy-peasy — stick to the basics like numbers or small objects.
  4. Nicking Tools from Others: Libraries like lodash have some nifty tools like isEqual for comparing stuff without all the drama of JSON.stringify.
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => ref.current = value);
return ref.current;
};

import _ from 'lodash';
import usePrevious from './usePrevious';
const example = (thisBook) => {
const thatBook = usePrevious(user);
useEffect(() => {
if (!_.isEqual(thatBook, thisBook)) {
console.log('Bingo!');
}
}, [thisBook]);
};
  1. useDeepCompareEffect: Kent C. Dodds, a right legend in the React gang, knocked up this nifty little custom hook called useDeepCompareEffect. It's a bit of a clever clogs for dealing with those tricky objects and arrays in your dependency lists.
import useDeepCompareEffect from 'use-deep-compare-effect';

const MyComponent = ({ complexObject }) => {
useDeepCompareEffect(() => {
console.log('Complex object has changed:', complexObject);
}, [complexObject]);
}

The Bottom Line

Using JSON.stringify in your deps array can be a quick fix, but it’s not always the bee’s knees. You’ve got to weigh up the pros and cons, and sometimes it’s better to rethink how you’re handling your state and props. Keep it clever and simple, and your app will be right as rain.

If you found this helpful, please consider sharing.

Thanks for reading, have fun!

--

--