Add custom React.memo comparator aware of fragment references

This commit is contained in:
Eloy Durán 2021-09-30 13:38:07 +02:00
Родитель 73528af5be
Коммит 316b55dcad
7 изменённых файлов: 92 добавлений и 9 удалений

Просмотреть файл

@ -15,7 +15,7 @@ import {
useApolloClient,
useQuery as useApolloQuery,
} from "@apollo/client";
import { useExecuteAndWatchQuery } from "./useExecuteAndWatchQuery";
import { useExecuteAndWatchQuery } from "./move-to-libs/useExecuteAndWatchQuery";
import {
executionQueryDocument,
watchQueryDocument,

Просмотреть файл

@ -1,6 +1,7 @@
import React, { useCallback } from "react";
import { useFragment } from "@graphitation/apollo-react-relay-duct-tape";
import { graphql } from "@graphitation/graphql-js-tag";
import { shallowCompareFragmentReferences } from "./move-to-libs/shallowCompareFragmentReferences";
import useChangeTodoStatusMutation from "./useChangeTodoStatusMutation";
@ -60,5 +61,5 @@ const Todo: React.FC<{ todo: Todo_todoFragment$key }> = ({ todo: todoRef }) => {
(Todo as any).whyDidYouRender = true;
const MemoizedTodo = React.memo(Todo);
const MemoizedTodo = React.memo(Todo, shallowCompareFragmentReferences("todo"));
export { MemoizedTodo as Todo };

Просмотреть файл

@ -1,6 +1,7 @@
import React from "react";
import { useFragment } from "@graphitation/apollo-react-relay-duct-tape";
import { graphql } from "@graphitation/graphql-js-tag";
import { shallowCompareFragmentReferences } from "./move-to-libs/shallowCompareFragmentReferences";
import {
TodoList_todosFragment$key,
@ -54,5 +55,8 @@ const TodoList: React.FC<{ todos: TodoList_todosFragment$key }> = ({
(TodoList as any).whyDidYouRender = true;
const MemoizedTodoList = React.memo(TodoList);
const MemoizedTodoList = React.memo(
TodoList,
shallowCompareFragmentReferences("todos")
);
export { MemoizedTodoList as TodoList };

Просмотреть файл

@ -1,6 +1,7 @@
import React from "react";
import { useFragment } from "@graphitation/apollo-react-relay-duct-tape";
import { graphql } from "@graphitation/graphql-js-tag";
import { shallowCompareFragmentReferences } from "./move-to-libs/shallowCompareFragmentReferences";
import {
TodoListFooter_todosFragment$key,
@ -17,7 +18,7 @@ export const TodoListFooter_todosFragment = graphql`
}
`;
export const TodoListFooter: React.FC<{
const TodoListFooter: React.FC<{
todos: TodoListFooter_todosFragment$key;
}> = ({ todos: todosRef }) => {
// TODO: This needs to be replaced by the webpack loader
@ -43,3 +44,9 @@ export const TodoListFooter: React.FC<{
</footer>
);
};
const MemoizedTodoListFooter = React.memo(
TodoListFooter,
shallowCompareFragmentReferences("todos")
);
export { MemoizedTodoListFooter as TodoListFooter };

Просмотреть файл

@ -0,0 +1,3 @@
# TODO
These need to move into our libs

Просмотреть файл

@ -0,0 +1,69 @@
/**
* Modified from https://github.com/facebook/react/blob/201af81b0168cabea3cc07cd8201378a4fec4aaf/packages/shared/shallowEqual.js
* Copying is the suggested way, as mentioned here https://github.com/facebook/react/issues/16919
*/
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found at
* https://github.com/facebook/react/blob/201af81b0168cabea3cc07cd8201378a4fec4aaf/LICENSE
*/
import invariant from "invariant";
/**
* A custom React.memo() comparator function factory that can be used with
* components that use `useFragment` on a GraphQL type that implements the
* `Node` interface, in which case only the `id` value needs to be equal to
* avoid a re-render.
*
* @todo
* Support arrays with fragment references
*
* @param fragmentReferenceProps
* The props that refer to fragment references and should only be compared by
* their [Node] ids.
*
* @returns
* A comparator with parameters typed such that TS can verify the component
* passed to React.memo() has props that match.
*/
export function shallowCompareFragmentReferences<K extends string>(
...fragmentReferenceProps: K[]
) {
return (prevProps: Record<K, any>, nextProps: Record<K, any>) => {
if (Object.is(prevProps, nextProps)) {
return true;
}
const keysPrev = Object.keys(prevProps);
const keysNext = Object.keys(nextProps);
if (keysPrev.length !== keysNext.length) {
return false;
}
for (let i = 0; i < keysPrev.length; i++) {
const checkKey = keysPrev[i] as K;
if (
!nextProps.hasOwnProperty(checkKey) ||
fragmentReferenceProps.includes(checkKey)
? !idsEqual(prevProps[checkKey], nextProps[checkKey])
: !Object.is(prevProps[checkKey], nextProps[checkKey])
) {
return false;
}
}
return true;
};
}
function idsEqual(objA: { id?: any }, objB: { id?: any }) {
invariant(
objA.id && objB.id,
"Expected both fragment reference objects to have an id field"
);
return objA.id === objB.id;
}

Просмотреть файл

@ -1,8 +1,3 @@
/**
* TODO: Rewrite this to mimic Relay's preload APIs and move it to
* @graphitation/apollo-react-relay-duct-tape.
*/
import {
useApolloClient,
ApolloQueryResult,
@ -11,6 +6,10 @@ import {
import { DocumentNode } from "graphql";
import { useRef, useState, useEffect } from "react";
/**
* TODO: Rewrite this to mimic Relay's preload APIs and move it to
* @graphitation/apollo-react-relay-duct-tape.
*/
export function useExecuteAndWatchQuery(
executionQuery: DocumentNode,
watchQuery: DocumentNode,