Skip to the code
cf.

shallowEqual

React skips re-rendering a memoized component when its new props are shallowly equal to its old ones. Write shallowEqual(a, b), which returns true when a and b are shallowly equal, and false otherwise.

If Object.is(a, b) is true, they're equal. Otherwise, both must be objects, not null, with the same own enumerable string keys (the ones Object.keys lists), in any order. For every key, the two values must be the same by Object.is.

Don't look inside the values: two different objects with the same contents are different values. Arrays are objects too, so two arrays are compared item by item.

const style = { color: "red" };
 
shallowEqual({ id: 1, style }, { id: 1, style });                   // true
shallowEqual({ id: 1, style }, { id: 1, style: { color: "red" } }); // false: a different object
shallowEqual({ a: 1, b: 2 }, { b: 2, a: 1 });                       // true
shallowEqual({ a: 1 }, { a: 1, b: undefined });                     // false: the first has no b
shallowEqual([1, 2], [1, 2]);                                       // true
shallowEqual(NaN, NaN);                                             // true

What the tests check

  1. returns true for the same keys with the same values, in any order
    expect(shallowEqual({ a: 1, b: "x" }, { a: 1, b: "x" })).toBe(true);
    expect(shallowEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true);
    expect(shallowEqual({}, {})).toBe(true);
    
  2. compares nested objects by reference, not by contents
    const style = { color: "red" };
    expect(shallowEqual({ style }, { style })).toBe(true);
    expect(shallowEqual({ style }, { style: { color: "red" } })).toBe(false);
    expect(shallowEqual({ list: [] }, { list: [] })).toBe(false);
    
  3. returns false when a value differs
    expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false);
    expect(shallowEqual({ a: 1 }, { a: "1" })).toBe(false);
    expect(shallowEqual({ a: null }, { a: undefined })).toBe(false);
    
  4. returns false for missing or extra keys
    expect(shallowEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
    expect(shallowEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
    expect(shallowEqual({ a: undefined }, { b: undefined })).toBe(false);
    expect(shallowEqual({ a: undefined }, {})).toBe(false);
    expect(shallowEqual({}, { a: undefined })).toBe(false);
    
  5. compares values with Object.is
    expect(shallowEqual({ a: NaN }, { a: NaN })).toBe(true);
    expect(shallowEqual({ a: 0 }, { a: -0 })).toBe(false);
    expect(shallowEqual(NaN, NaN)).toBe(true);
    expect(shallowEqual(0, -0)).toBe(false);
    
  6. handles primitives, null, and the same object twice
    const props = { a: { b: 1 } };
    expect(shallowEqual(props, props)).toBe(true);
    expect(shallowEqual(1, 1)).toBe(true);
    expect(shallowEqual("a", "a")).toBe(true);
    expect(shallowEqual(1, "1")).toBe(false);
    expect(shallowEqual(null, null)).toBe(true);
    expect(shallowEqual(null, {})).toBe(false);
    expect(shallowEqual({}, null)).toBe(false);
    expect(shallowEqual(undefined, null)).toBe(false);
    
  7. compares arrays item by item
    const item = { id: 1 };
    expect(shallowEqual([1, 2], [1, 2])).toBe(true);
    expect(shallowEqual([item], [item])).toBe(true);
    expect(shallowEqual([1, 2], [1, 2, 3])).toBe(false);
    expect(shallowEqual([{ id: 1 }], [{ id: 1 }])).toBe(false);
    
  8. ignores inherited and non-enumerable properties
    expect(shallowEqual(Object.create({ a: 1 }), {})).toBe(true);
    expect(shallowEqual({}, Object.create({ a: 1 }))).toBe(true);
    const inherited = Object.assign(Object.create({ a: 1 }), { b: 1 });
    expect(shallowEqual({ a: 1 }, inherited)).toBe(false);
    const hidden = Object.defineProperty({ a: 1 }, "secret", { value: 2, enumerable: false });
    expect(shallowEqual(hidden, { a: 1 })).toBe(true);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).