Skip to content

Commit 897686b

Browse files
[wb1812.2.idcomponent] Add the Id component (#2389)
## Summary: This adds a simple CaaF (children-as-a-function) component to generate unique IDs as a stand-in for the `useId` hook. This is useful for cases where one needs to generate unique IDs for a component, but cannot use the hook without a larger refactor. Issue: WB-1812 ## Test plan: `yarn test` `yarn start:storybook` to check for the added docs `yarn typecheck` Author: somewhatabstract Reviewers: jeresig Required Reviewers: Approved By: jeresig Checks: ⌛ Prime node_modules cache for primary configuration (ubuntu-latest, 20.x), ⌛ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ⌛ gerald, ⏭️ dependabot Pull Request URL: #2389
1 parent b6009b7 commit 897686b

File tree

5 files changed

+114
-0
lines changed

5 files changed

+114
-0
lines changed

.changeset/thirty-jars-grow.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-core": minor
3+
---
4+
5+
- Add the `Id` component for cases where `useId` cannot be used directly
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from "react";
2+
3+
import {Meta} from "@storybook/react";
4+
import {View, Id} from "@khanacademy/wonder-blocks-core";
5+
import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
6+
import {Strut} from "@khanacademy/wonder-blocks-layout";
7+
import {spacing} from "@khanacademy/wonder-blocks-tokens";
8+
9+
export default {
10+
title: "Packages / Core / Id",
11+
12+
parameters: {
13+
chromatic: {
14+
// We don't need a snapshot for this.
15+
disableSnapshot: true,
16+
},
17+
},
18+
} as Meta;
19+
20+
export const GeneratedIdExample = () => (
21+
<View>
22+
<Id>
23+
{(id) => (
24+
<View style={{flexDirection: "row"}}>
25+
<Body>Generated identifier: </Body>
26+
<Strut size={spacing.xSmall_8} />
27+
<BodyMonospace>{id}</BodyMonospace>
28+
</View>
29+
)}
30+
</Id>
31+
</View>
32+
);
33+
34+
export const PassedThroughIdExample = () => (
35+
<View>
36+
<Id id="my-identifier">
37+
{(id) => (
38+
<View style={{flexDirection: "row"}}>
39+
<Body>Passed through identifier: </Body>
40+
<Strut size={spacing.xSmall_8} />
41+
<BodyMonospace>{id}</BodyMonospace>
42+
</View>
43+
)}
44+
</Id>
45+
</View>
46+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from "react";
2+
3+
import {render} from "@testing-library/react";
4+
import {Id} from "../id";
5+
6+
describe("Id", () => {
7+
it("should provide an id to the children", () => {
8+
// Arrange
9+
const childrenFn = jest.fn().mockReturnValue(null);
10+
11+
// Act
12+
render(<Id>{childrenFn}</Id>);
13+
14+
// Assert
15+
expect(childrenFn).toHaveBeenCalledWith(expect.any(String));
16+
});
17+
18+
it("should pass through the given id to the children", () => {
19+
// Arrange
20+
const childrenFn = jest.fn().mockReturnValue(null);
21+
22+
// Act
23+
render(<Id id="my-id">{childrenFn}</Id>);
24+
25+
// Assert
26+
expect(childrenFn).toHaveBeenCalledWith("my-id");
27+
});
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {useId} from "react";
2+
import * as React from "react";
3+
4+
type Props = {
5+
/**
6+
* An identifier to use.
7+
*
8+
* If this is omitted, an identifier is generated.
9+
*/
10+
id?: string | undefined;
11+
12+
/**
13+
* A function that to render children with the given identifier.
14+
*/
15+
children: (id: string) => React.ReactNode;
16+
};
17+
18+
/**
19+
* A component that provides an identifier to its children.
20+
*
21+
* If an `id` prop is provided, that is passed through to the children;
22+
* otherwise, a unique identifier is generated.
23+
*/
24+
export const Id = ({id, children}: Props) => {
25+
const generatedId = useId();
26+
27+
// If we already have an ID, then use that.
28+
// Otherwise, use the generated ID.
29+
// NOTE: We can't call hooks conditionally, but it should be pretty cheap
30+
// to call useId() and not use the result, rather than the alternative
31+
// which would be to have a separate component for cases where we need
32+
// to call the hook and then render the component conditionally.
33+
return <>{children(id ?? generatedId)}</>;
34+
};

packages/wonder-blocks-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export {default as IDProvider} from "./components/id-provider";
1313
export {default as UniqueIDProvider} from "./components/unique-id-provider";
1414
export {default as addStyle} from "./util/add-style";
1515
export {default as Server} from "./util/server";
16+
export {Id} from "./components/id";
1617
export {
1718
useUniqueIdWithMock,
1819
useUniqueIdWithoutMock,

0 commit comments

Comments
 (0)