back to blog
February 14, 2022 — 5 mins read
Share:

How React checks if 2 values are the same value

#react#javascript#beginners#webdev

If you have ever wondered how react internally checks if two values are the same value this is the right blog for you. Well, before we even start looking at how React implements that lets first check to see if we already have a function in plain javascript that does that.

We already have this function Object.is() for that, and according to MDN Web docs Object.is() determines whether two values are the same value. Two values are the same if one of the following holds:

- both undefined
- both null
- both true or both false
- both strings of the same length with the same characters in the same order
- both the same object (meaning both values reference the same object in memory)
- both numbers and
   - both +0
   - both -0
   - both NaN
   - or both non-zero and both not NaN and both have the same value
Enter fullscreen mode Exit fullscreen mode

Note that Object.is() is not the same as the == operator as it does not do any type coercion if the types of values are not the same. It's also not the same as the === operator because they treat NaNs and signed zeros differently. With the === or the == operators -0 and +0 are treated as equal. The === operator also treat NaN and Number.NaN as not equal.

Example usage from MDN Web docs

// Case 1: Evaluation result is the same as using ===
Object.is(25, 25);                // true
Object.is('foo', 'foo');          // true
Object.is('foo', 'bar');          // false
Object.is(null, null);            // true
Object.is(undefined, undefined);  // true
Object.is(window, window);        // true
Object.is([], []);                // false
var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);              // true
Object.is(foo, bar);              // false

// Case 2: Signed zero
Object.is(0, -0);                 // false
Object.is(+0, -0);                // false
Object.is(-0, -0);                // true
Object.is(0n, -0n);               // true

// Case 3: NaN
Object.is(NaN, 0/0);              // true
Object.is(NaN, Number.NaN)        // true
Enter fullscreen mode Exit fullscreen mode

So this is how Object.is() works, but since we don't live in a perfect world React can't just use this implementation, they need to have some sort of a polyfill for environments like old browsers that don't support Object.is(). Let paste the polyfill code from React source code and go through it.

function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}
Enter fullscreen mode Exit fullscreen mode

This function is inline, but to make sure that everyone understands let me put it in a long format.

function is(x: any, y: any) {
      // SameValue algorithm
      if (x === y) {
        // return true if x and y are not 0, OR
        // if x and y are both 0 of the same sign.
        return x !== 0 || 1 / x === 1 / y;
      } else {
        // return true if both x AND y evaluate to NaN.
        // The only possibility for a variable to not be strictly equal to itself
        // is when that variable evaluates to NaN (example: Number.NaN, 0/0, NaN).
        return x !== x && y !== y;
      }
}
Enter fullscreen mode Exit fullscreen mode

The if condition is hit if the values we pass are equal for example if we pass 2 and 2 the condition immediately returns with true because 2 is not equal to 0.

If we pass +0 and -0 the OR part of the return statement is evaluated and 1/0 which is equal to Infinity will be compared to 1/-0 which is -Infinity which will then evaluate to false. This makes sure that even if we pass zeros they are of the same sign.

All that can be refactored to what we have below. If this looks cryptic to you let me know in the comments.

(x === y && (x !== 0 || 1 / x === 1 / y))
Enter fullscreen mode Exit fullscreen mode

For the else part of our function there is a possibility for the function to return true, that is if both x AND y evaluate to NaN, otherwise return false.

Below is the whole React source file for what we went through.

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}

const objectIs: (x: any, y: any) => boolean =
  typeof Object.is === 'function' ? Object.is : is;

export default objectIs;
Enter fullscreen mode Exit fullscreen mode

Now the question you may have is where are we going to use this? Well, there is another function that uses this method in React called shallowEqual. The result from this function is what causes React to rerender. Let's say you pass a name prop to a component with value=Jose and then later on change the value to Joseph React compares these values using the shallowEqual function and if it returns false, react rerenders.

I am going to paste the code for this function as it is from React source code and go through it. Also note our objectIs function is imported as just is.

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    const currentKey = keysA[i];
    if (
      !hasOwnProperty.call(objB, currentKey) ||
      !is(objA[currentKey], objB[currentKey])
    ) {
      return false;
    }
  }

  return true;
}

export default shallowEqual;
Enter fullscreen mode Exit fullscreen mode

This function accepts 2 arguments and if we pass them to objectIs and they return true, the function immediately returns with true. This is covered by the first if statement.

If any of the objects we pass is null, or its type is not equal to object the function returns with false. That's covered by the second if statement.

If the objects we pass have different number of properties, we also return false that means they are not equal. That's covered by the third if statement which checks the length of keys to see if they are not equal.

Now the last part is to check if the keys are the same for both objects. We use the Object.hasOwnProperty for that for each key. objA and objB may have the same number of keys and Object.hasOwnProperty makes sure that even if the number of keys is the same the keys also have the same names.

Please Note this is a shallow comparison so we don't compare the values in the objects so if you mutate an object and pass it React will not be able to see that change.

Thanks for reading, stay tuned for more articles about these small react functions.

Copyright © 2022 | All rights reserved.

Made with ❤️ in Zimbabwe🇿🇼 by Joseph Mukorivo.