useStructuralSharing.tsx 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. import {useMemo, useRef} from 'react';
  2. /**
  3. * Check if two objects have the same keys
  4. */
  5. const checkSameKeys = (obj1: any, obj2: any) => {
  6. const keys1 = new Set(Object.keys(obj1));
  7. const keys2 = new Set(Object.keys(obj2));
  8. if (keys1.size !== keys2.size) {
  9. return false;
  10. }
  11. for (const key in keys1) {
  12. if (!keys2.has(key)) {
  13. return false;
  14. }
  15. }
  16. return true;
  17. };
  18. /**
  19. * Merge oldVlaue and newValue while trying to preserve references of unchanged objects / arrays
  20. */
  21. export function structuralSharing<T>(oldValue: T, newValue: T): T {
  22. if (oldValue === newValue) {
  23. return oldValue;
  24. }
  25. if (Array.isArray(oldValue) && Array.isArray(newValue)) {
  26. let hasChanges = oldValue.length !== newValue.length;
  27. const newArray = newValue.map((item, index) => {
  28. const newItem = structuralSharing(oldValue[index], item);
  29. if (newItem !== oldValue[index]) {
  30. hasChanges = true;
  31. }
  32. return newItem;
  33. });
  34. return hasChanges ? (newArray as any) : oldValue;
  35. }
  36. if (oldValue === null || newValue === null) {
  37. return newValue;
  38. }
  39. if (typeof oldValue === 'object' && typeof newValue === 'object') {
  40. let hasChanges = !checkSameKeys(oldValue, newValue);
  41. const newObj = Object.keys(newValue).reduce((acc, key) => {
  42. acc[key] = structuralSharing(oldValue[key], newValue[key]);
  43. if (acc[key] !== oldValue[key]) {
  44. hasChanges = true;
  45. }
  46. return acc;
  47. }, {});
  48. return hasChanges ? (newObj as any) : oldValue;
  49. }
  50. return newValue;
  51. }
  52. export function useStructuralSharing<T>(value: T): T {
  53. const previousValue = useRef<T>(value);
  54. return useMemo(() => {
  55. const newValue = structuralSharing(previousValue.current, value);
  56. previousValue.current = newValue;
  57. return newValue;
  58. }, [value]);
  59. }