pacocoursey / cmdk

Fast, unstyled command menu React component.
https://cmdk.paco.me
MIT License
9.04k stars 259 forks source link

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

Open kimskovhusandersen opened 11 months ago

kimskovhusandersen commented 11 months ago

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.

phsantiago commented 11 months ago

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.

rafaell-lycan commented 3 months ago

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