Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dependency to reach-ui #1002

Merged
merged 5 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)[
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an any warning here. Is that going to annoy us going forward?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I've just added an eslint-disable-next-line @typescript-eslint/no-explicit-any above this line

'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