useStructuralSharing.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  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. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  43. acc[key] = structuralSharing(oldValue[key], newValue[key]);
  44. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  45. if (acc[key] !== oldValue[key]) {
  46. hasChanges = true;
  47. }
  48. return acc;
  49. }, {});
  50. return hasChanges ? (newObj as any) : oldValue;
  51. }
  52. return newValue;
  53. }
  54. export function useStructuralSharing<T>(value: T): T {
  55. const previousValue = useRef<T>(value);
  56. return useMemo(() => {
  57. const newValue = structuralSharing(previousValue.current, value);
  58. previousValue.current = newValue;
  59. return newValue;
  60. }, [value]);
  61. }