Home Reference Source Repository

src/container/RelayContainerComparators.js

/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayContainerComparators
 * @flow
 */

'use strict';

/**
 * Compares `objectA` and `objectB` using the provided `isEqual` function.
 *
 * If a `filter` object is provided, only its keys will be checked during
 * comparison.
 */
function compareObjects(
  isEqual: (propA: any, propB: any, key: string) => boolean,
  objectA: Object,
  objectB: Object,
  filter?: Object
): boolean {
  let key;

  // Test for A's keys different from B.
  for (key in objectA) {
    if (filter && !filter.hasOwnProperty(key)) {
      continue;
    }

    if (objectA.hasOwnProperty(key) && (
          !objectB.hasOwnProperty(key) ||
          !isEqual(objectA[key], objectB[key], key))) {
      return false;
    }
  }
  // Test for B's keys missing from A.
  for (key in objectB) {
    if (filter && !filter.hasOwnProperty(key)) {
      continue;
    }

    if (objectB.hasOwnProperty(key) && !objectA.hasOwnProperty(key)) {
      return false;
    }
  }
  return true;
}

function isScalarAndEqual(valueA, valueB) {
  return valueA === valueB && (valueA === null || typeof valueA !== 'object');
}

function isQueryDataEqual(
  fragmentPointers: Object,
  currProp: mixed,
  nextProp: mixed,
  propName: string
): boolean {
  return (
    // resolved data did not change
    (fragmentPointers[propName] && currProp === nextProp) ||
    // otherwise compare fake data
    isScalarAndEqual(currProp, nextProp)
  );
}

function isNonQueryPropEqual(
  fragments: Object,
  currProp: mixed,
  nextProp: mixed,
  propName: string
): boolean {
  return (
    // ignore props with fragments (instead resolved values are compared)
    fragments.hasOwnProperty(propName) ||
    // otherwise props must be scalar and === in order to skip
    isScalarAndEqual(currProp, nextProp)
  );
}

/**
 * Relay-aware comparators for props and state provide a reasonable default
 * implementation of `shouldComponentUpdate`.
 */
const RelayContainerComparators = {
  areQueryResultsEqual(
    fragmentPointers: Object,
    prevQueryData: Object,
    nextQueryData: Object
  ): boolean {
    return compareObjects(
      isQueryDataEqual.bind(null, fragmentPointers),
      prevQueryData,
      nextQueryData
    );
  },

  areNonQueryPropsEqual(
    fragments: Object,
    props: Object,
    nextProps: Object
  ): boolean {
    return compareObjects(
      isNonQueryPropEqual.bind(null, fragments),
      props,
      nextProps
    );
  },

  areQueryVariablesEqual(
    variables: Object,
    nextVariables: Object
  ): boolean {
    return compareObjects(isScalarAndEqual, variables, nextVariables);
  },
};

module.exports = RelayContainerComparators;