cmdk icon indicating copy to clipboard operation
cmdk copied to clipboard

Request for Backward Compatibility - React.useId and React.useSyncExternalStore

Open kimskovhusandersen opened this issue 1 year ago • 3 comments

Could you kindly consider adding backward compatibility for React.useId and React.useSyncExternalStore?

These hooks were introduced in React 18, but it would be incredibly beneficial for developers still using older React versions to be able to the library without having to upgrade to React 18.

Thank you for your time and consideration. I appreciate the effort you put into making cmdk a great library.

kimskovhusandersen avatar Jul 26 '23 11:07 kimskovhusandersen

I'm facing the same problem here. You can monkey patch like this:

import { Command as CommandPrimitive } from 'cmdk';

function is(x: any, y: any) {
  return (
    (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
  );
}
let counter = 0;
(React as any).useId = () => 'cmdk' + (counter++).toString(32);
function checkIfSnapshotChanged<T>(inst: {
  value: T;
  getSnapshot: () => T;
}): boolean {
  const latestGetSnapshot = inst.getSnapshot;
  const prevValue = inst.value;
  try {
    const nextValue = latestGetSnapshot();
    return !is(prevValue, nextValue);
  } catch (error) {
    return true;
  }
}
function useSyncExternalStore<T>(subscribe: any, getSnapshot: () => T): T {
  const value = getSnapshot();
  const [{ inst }, forceUpdate] = React.useState({
    inst: { value, getSnapshot },
  });

  React.useLayoutEffect(() => {
    inst.value = value;
    inst.getSnapshot = getSnapshot;

    if (checkIfSnapshotChanged(inst)) {
      forceUpdate({ inst });
    }
  }, [subscribe, value, getSnapshot]);

  React.useEffect(() => {
    if (checkIfSnapshotChanged(inst)) {
      forceUpdate({ inst });
    }
    const handleStoreChange = () => {
      if (checkIfSnapshotChanged(inst)) {
        forceUpdate({ inst });
      }
    };
    return subscribe(handleStoreChange);
  }, [subscribe]);

  return value;
}
(React as any).useSyncExternalStore = useSyncExternalStore;

I see radix adding compatibility this way: https://github.com/radix-ui/primitives/blob/eca6babd188df465f64f23f3584738b85dba610e/packages/react/id/src/id.tsx

We can do the same here.

phsantiago avatar Jul 31 '23 12:07 phsantiago

@kimskovhusandersen you can also patch with use-sync-external-store as described above to avoid this problem.

rafaell-lycan avatar Mar 18 '24 13:03 rafaell-lycan

@rafaell-lycan @phsantiago @kimskovhusandersen I created a PR to fix the use of useSyncExternalStore

MateoKruk avatar Aug 05 '24 20:08 MateoKruk