Skip to content

Commit

Permalink
[wb1812.2.idcomponent] Add the Id component (#2389)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
somewhatabstract authored Dec 16, 2024
1 parent b6009b7 commit 897686b
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-jars-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/wonder-blocks-core": minor
---

- Add the `Id` component for cases where `useId` cannot be used directly
46 changes: 46 additions & 0 deletions __docs__/wonder-blocks-core/id.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react";

import {Meta} from "@storybook/react";
import {View, Id} from "@khanacademy/wonder-blocks-core";
import {Body, BodyMonospace} from "@khanacademy/wonder-blocks-typography";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {spacing} from "@khanacademy/wonder-blocks-tokens";

export default {
title: "Packages / Core / Id",

parameters: {
chromatic: {
// We don't need a snapshot for this.
disableSnapshot: true,
},
},
} as Meta;

export const GeneratedIdExample = () => (
<View>
<Id>
{(id) => (
<View style={{flexDirection: "row"}}>
<Body>Generated identifier: </Body>
<Strut size={spacing.xSmall_8} />
<BodyMonospace>{id}</BodyMonospace>
</View>
)}
</Id>
</View>
);

export const PassedThroughIdExample = () => (
<View>
<Id id="my-identifier">
{(id) => (
<View style={{flexDirection: "row"}}>
<Body>Passed through identifier: </Body>
<Strut size={spacing.xSmall_8} />
<BodyMonospace>{id}</BodyMonospace>
</View>
)}
</Id>
</View>
);
28 changes: 28 additions & 0 deletions packages/wonder-blocks-core/src/components/__tests__/id.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as React from "react";

import {render} from "@testing-library/react";
import {Id} from "../id";

describe("Id", () => {
it("should provide an id to the children", () => {
// Arrange
const childrenFn = jest.fn().mockReturnValue(null);

// Act
render(<Id>{childrenFn}</Id>);

// Assert
expect(childrenFn).toHaveBeenCalledWith(expect.any(String));
});

it("should pass through the given id to the children", () => {
// Arrange
const childrenFn = jest.fn().mockReturnValue(null);

// Act
render(<Id id="my-id">{childrenFn}</Id>);

// Assert
expect(childrenFn).toHaveBeenCalledWith("my-id");
});
});
34 changes: 34 additions & 0 deletions packages/wonder-blocks-core/src/components/id.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useId} from "react";
import * as React from "react";

type Props = {
/**
* An identifier to use.
*
* If this is omitted, an identifier is generated.
*/
id?: string | undefined;

/**
* A function that to render children with the given identifier.
*/
children: (id: string) => React.ReactNode;
};

/**
* A component that provides an identifier to its children.
*
* If an `id` prop is provided, that is passed through to the children;
* otherwise, a unique identifier is generated.
*/
export const Id = ({id, children}: Props) => {
const generatedId = useId();

// If we already have an ID, then use that.
// Otherwise, use the generated ID.
// NOTE: We can't call hooks conditionally, but it should be pretty cheap
// to call useId() and not use the result, rather than the alternative
// which would be to have a separate component for cases where we need
// to call the hook and then render the component conditionally.
return <>{children(id ?? generatedId)}</>;
};
1 change: 1 addition & 0 deletions packages/wonder-blocks-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {default as IDProvider} from "./components/id-provider";
export {default as UniqueIDProvider} from "./components/unique-id-provider";
export {default as addStyle} from "./util/add-style";
export {default as Server} from "./util/server";
export {Id} from "./components/id";
export {
useUniqueIdWithMock,
useUniqueIdWithoutMock,
Expand Down

0 comments on commit 897686b

Please sign in to comment.