Skip to content

Commit

Permalink
Remove dependency to reach-ui (#1002)
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanoverbye authored Mar 8, 2023
1 parent fcc5cde commit 42d8d71
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-news-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ag.ds-next/react': minor
---

Removed dependency to Reach UI as it is no longer being maintained. The logic from `@reach/auto-id` has been forked and copied into the repo.
1 change: 0 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"dependencies": {
"@babel/runtime": "^7.4.5",
"@popperjs/core": "^2.11.4",
"@reach/auto-id": "^0.18.0",
"@react-spring/web": "^9.4.5",
"date-fns": "^2.28.0",
"downshift": "^6.1.7",
Expand Down
1 change: 0 additions & 1 deletion packages/react/src/core/utils/useId.ts

This file was deleted.

9 changes: 9 additions & 0 deletions packages/react/src/core/utils/useId/canUseDom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// The contents of this file has been copied from https://github.com/reach/reach-ui/blob/dev/packages/utils/src/can-use-dom.ts

export function canUseDOM() {
return !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
}
1 change: 1 addition & 0 deletions packages/react/src/core/utils/useId/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useId } from './useId';
35 changes: 35 additions & 0 deletions packages/react/src/core/utils/useId/useId.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// The contents of this file has been copied from https://github.com/reach/reach-ui/blob/dev/packages/auto-id/__tests__/auto-id.test.tsx

import { render, cleanup } from '../../../../../../test-utils';
import { useId } from './useId';

afterEach(cleanup);

describe('useId', () => {
it('should generate a unique ID value', () => {
function Comp() {
const justNull = null;
const randId = useId(justNull);
const randId2 = useId();
return (
<div>
<div id={randId}>Wow</div>
<div id={randId2}>Ok</div>
</div>
);
}
const { getByText } = render(<Comp />);
const id1 = getByText('Wow').id;
const id2 = getByText('Ok').id;
expect(id2).not.toEqual(id1);
});

it('uses a fallback ID', () => {
function Comp() {
const newId = useId('awesome');
return <div id={newId}>Ok</div>;
}
const { getByText } = render(<Comp />);
expect(getByText('Ok').id).toEqual('awesome');
});
});
139 changes: 139 additions & 0 deletions packages/react/src/core/utils/useId/useId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* This file use to just be a simple re-export of `useId` from @reach/auto-id
* We can not just use `useId` from React as we need to provide support for React 16, 17 and 18
*
* As Reach is no longer being maintained and does not support React 18, the contents of this file have been copied from:
* https://github.com/reach/reach-ui/blob/dev/packages/auto-id/src/reach-auto-id.ts
*
* See Github issue: https://github.com/reach/reach-ui/issues/972
*/

/**
* Welcome to @reach/auto-id!
* Let's see if we can make sense of why this hook exists and its
* implementation.
*
* Some background:
* 1. Accessibility APIs rely heavily on element IDs
* 2. Requiring developers to put IDs on every element in Reach UI is both
* cumbersome and error-prone
* 3. With a component model, we can generate IDs for them!
*
* Solution 1: Generate random IDs.
*
* This works great as long as you don't server render your app. When React (in
* the client) tries to reuse the markup from the server, the IDs won't match
* and React will then recreate the entire DOM tree.
*
* Solution 2: Increment an integer
*
* This sounds great. Since we're rendering the exact same tree on the server
* and client, we can increment a counter and get a deterministic result between
* client and server. Also, JS integers can go up to nine-quadrillion. I'm
* pretty sure the tab will be closed before an app never needs
* 10 quadrillion IDs!
*
* Problem solved, right?
*
* Ah, but there's a catch! React's concurrent rendering makes this approach
* non-deterministic. While the client and server will end up with the same
* elements in the end, depending on suspense boundaries (and possibly some user
* input during the initial render) the incrementing integers won't always match
* up.
*
* Solution 3: Don't use IDs at all on the server; patch after first render.
*
* What we've done here is solution 2 with some tricks. With this approach, the
* ID returned is an empty string on the first render. This way the server and
* client have the same markup no matter how wild the concurrent rendering may
* have gotten.
*
* After the render, we patch up the components with an incremented ID. This
* causes a double render on any components with `useId`. Shouldn't be a problem
* since the components using this hook should be small, and we're only updating
* the ID attribute on the DOM, nothing big is happening.
*
* It doesn't have to be an incremented number, though--we could do generate
* random strings instead, but incrementing a number is probably the cheapest
* thing we can do.
*
* Additionally, we only do this patchup on the very first client render ever.
* Any calls to `useId` that happen dynamically in the client will be
* populated immediately with a value. So, we only get the double render after
* server hydration and never again, SO BACK OFF ALRIGHT?
*/

/* eslint-disable react-hooks/rules-of-hooks */

import * as React from 'react';
import { useIsomorphicLayoutEffect as useLayoutEffect } from './useIsomorphicLayoutEffect';

let serverHandoffComplete = false;
let id = 0;
function genId() {
return ++id;
}

// Workaround for https://github.com/webpack/webpack/issues/14814
// https://github.com/eps1lon/material-ui/blob/8d5f135b4d7a58253a99ab56dce4ac8de61f5dc1/packages/mui-utils/src/useId.ts#L21
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maybeReactUseId: undefined | (() => string) = (React as any)[
'useId'.toString()
];

/**
* useId
*
* Autogenerate IDs to facilitate WAI-ARIA and server rendering.
*
* Note: The returned ID will initially be `null` and will update after a
* component mounts. Users may need to supply their own ID if they need
* consistent values for SSR.
*
* @see Docs https://reach.tech/auto-id
*/
function useId(idFromProps: string): string;
function useId(idFromProps: number): number;
function useId(idFromProps: string | number): string | number;
function useId(idFromProps: string | undefined | null): string | undefined;
function useId(idFromProps: number | undefined | null): number | undefined;
function useId(
idFromProps: string | number | undefined | null
): string | number | undefined;
function useId(): string | undefined;

function useId(providedId?: number | string | undefined | null) {
if (maybeReactUseId !== undefined) {
const generatedId = maybeReactUseId();
return providedId ?? generatedId;
}

// If this instance isn't part of the initial render, we don't have to do the
// double render/patch-up dance. We can just generate the ID and return it.
const initialId = providedId ?? (serverHandoffComplete ? genId() : null);
const [id, setId] = React.useState(initialId);

useLayoutEffect(() => {
if (id === null) {
// Patch the ID after render. We do this in `useLayoutEffect` to avoid any
// rendering flicker, though it'll make the first render slower (unlikely
// to matter, but you're welcome to measure your app and let us know if
// it's a problem).
setId(genId());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React.useEffect(() => {
if (serverHandoffComplete === false) {
// Flag all future uses of `useId` to skip the update dance. This is in
// `useEffect` because it goes after `useLayoutEffect`, ensuring we don't
// accidentally bail out of the patch-up dance prematurely.
serverHandoffComplete = true;
}
}, []);

return providedId ?? id ?? undefined;
}

export { useId };
32 changes: 32 additions & 0 deletions packages/react/src/core/utils/useId/useIsomorphicLayoutEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// The contents of this file has been copied from https://github.com/reach/reach-ui/blob/dev/packages/utils/src/use-isomorphic-layout-effect.ts

import { useEffect, useLayoutEffect } from 'react';
import { canUseDOM } from './canUseDom';

/**
* React currently throws a warning when using useLayoutEffect on the server. To
* get around it, we can conditionally useEffect on the server (no-op) and
* useLayoutEffect in the browser. We occasionally need useLayoutEffect to
* ensure we don't get a render flash for certain operations, but we may also
* need affected components to render on the server. One example is when setting
* a component's descendants to retrieve their index values.
*
* Important to note that using this hook as an escape hatch will break the
* eslint dependency warnings unless you rename the import to `useLayoutEffect`.
* Use sparingly only when the effect won't effect the rendered HTML to avoid
* any server/client mismatch.
*
* If a useLayoutEffect is needed and the result would create a mismatch, it's
* likely that the component in question shouldn't be rendered on the server at
* all, so a better approach would be to lazily render those in a parent
* component after client-side hydration.
*
* https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
* https://github.com/reduxjs/react-redux/blob/master/src/utils/useIsomorphicLayoutEffect.js
*
* @param effect
* @param deps
*/
export const useIsomorphicLayoutEffect = canUseDOM()
? useLayoutEffect
: useEffect;
12 changes: 0 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2253,18 +2253,6 @@
resolved "https://registry.yarnpkg.com/@preconstruct/next/-/next-4.0.0.tgz#4d9c64ed68bb7cdc72d35d79d1dbe26ba2cae61e"
integrity sha512-vSrc8wFQgBErU7dKTKSQtr/DLWPHcN9jMoiWOAQodB1+B4Kpqqry6QhGYoRm0DQU5gNL+Rcp+Xb350O1E/gjsg==

"@reach/auto-id@^0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.18.0.tgz#4b97085cd1cf1360a9bedc6e9c78e97824014f0d"
integrity sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg==
dependencies:
"@reach/utils" "0.18.0"

"@reach/[email protected]":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.18.0.tgz#4f3cebe093dd436eeaff633809bf0f68f4f9d2ee"
integrity sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==

"@react-spring/animated@~9.4.5":
version "9.4.5"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.4.5.tgz#dd9921c716a4f4a3ed29491e0c0c9f8ca0eb1a54"
Expand Down

0 comments on commit 42d8d71

Please sign in to comment.