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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useUniqueId hook and fix several needlessly incrementing ids #2079

Merged
merged 4 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f
### Code quality

- Migrated `ActionMenu.RollupAction`, `Autocomplete`, `Card`, `EmptySearchResult`, `Form`, `SkeletonPage` and `TopBar` to use hooks instead of withAppProvider ([#2065](https://github.com/Shopify/polaris-react/pull/2065))
- Added `useUniqueId` hook that can be used to get a unique id that remains consistent between rerenders and updated components to use it where appropriate ([#2079](https://github.com/Shopify/polaris-react/pull/2079))

### Deprecations
24 changes: 17 additions & 7 deletions src/components/AppProvider/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import {
StickyManagerContext,
} from '../../utilities/sticky-manager';
import {LinkContext, LinkLikeComponent} from '../../utilities/link';
import {
UniqueIdFactory,
UniqueIdFactoryContext,
globalIdGeneratorFactory,
} from '../../utilities/unique-id';

interface State {
intl: I18n;
Expand All @@ -35,11 +40,14 @@ export interface AppProviderProps extends AppBridgeOptions {
export class AppProvider extends React.Component<AppProviderProps, State> {
private stickyManager: StickyManager;
private scrollLockManager: ScrollLockManager;
private uniqueIdFactory: UniqueIdFactory;

constructor(props: AppProviderProps) {
super(props);
this.stickyManager = new StickyManager();
this.scrollLockManager = new ScrollLockManager();
this.uniqueIdFactory = new UniqueIdFactory(globalIdGeneratorFactory);

const {i18n, apiKey, shopOrigin, forceRedirect, linkComponent} = this.props;

// eslint-disable-next-line react/state-in-constructor
Expand Down Expand Up @@ -91,13 +99,15 @@ export class AppProvider extends React.Component<AppProviderProps, State> {
<I18nContext.Provider value={intl}>
<ScrollLockManagerContext.Provider value={this.scrollLockManager}>
<StickyManagerContext.Provider value={this.stickyManager}>
<AppBridgeContext.Provider value={appBridge}>
<LinkContext.Provider value={link}>
<ThemeProvider theme={theme}>
{React.Children.only(children)}
</ThemeProvider>
</LinkContext.Provider>
</AppBridgeContext.Provider>
<UniqueIdFactoryContext.Provider value={this.uniqueIdFactory}>
<AppBridgeContext.Provider value={appBridge}>
<LinkContext.Provider value={link}>
<ThemeProvider theme={theme}>
{React.Children.only(children)}
</ThemeProvider>
</LinkContext.Provider>
</AppBridgeContext.Provider>
</UniqueIdFactoryContext.Provider>
</StickyManagerContext.Provider>
</ScrollLockManagerContext.Provider>
</I18nContext.Provider>
Expand Down
7 changes: 3 additions & 4 deletions src/components/ChoiceList/ChoiceList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';

import {classNames} from '../../utilities/css';
import {useUniqueId} from '../../utilities/unique-id';
import {Checkbox} from '../Checkbox';
import {RadioButton} from '../RadioButton';
import {InlineError, errorTextID} from '../InlineError';
Expand Down Expand Up @@ -48,8 +48,6 @@ export interface ChoiceListProps {
onChange?(selected: string[], name: string): void;
}

const getUniqueID = createUniqueIDFactory('ChoiceList');

export function ChoiceList({
title,
titleHidden,
Expand All @@ -59,12 +57,13 @@ export function ChoiceList({
onChange = noop,
error,
disabled = false,
name = getUniqueID(),
name: nameProp,
}: ChoiceListProps) {
// Type asserting to any is required for TS3.2 but can be removed when we update to 3.3
// see https://github.com/Microsoft/TypeScript/issues/28768
const ControlComponent: any = allowMultiple ? Checkbox : RadioButton;

const name = useUniqueId('ChoiceList', nameProp);
const finalName = allowMultiple ? `${name}[]` : name;

const className = classNames(
Expand Down
6 changes: 2 additions & 4 deletions src/components/FormLayout/components/Group/Group.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';

import {classNames} from '../../../../utilities/css';
import {wrapWithComponent} from '../../../../utilities/components';
import {useUniqueId} from '../../../../utilities/unique-id';
import styles from '../../FormLayout.scss';
import {Item} from '../Item';

Expand All @@ -13,12 +13,10 @@ export interface GroupProps {
helpText?: React.ReactNode;
}

const getUniqueID = createUniqueIDFactory('FormLayoutGroup');

export function Group({children, condensed, title, helpText}: GroupProps) {
const className = classNames(condensed ? styles.condensed : styles.grouped);

const id = getUniqueID();
const id = useUniqueId('FormLayoutGroup');

let helpTextElement = null;
let helpTextID: undefined | string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import React from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';

import {useUniqueId} from '../../../../../../utilities/unique-id';
import {Collapsible} from '../../../../../Collapsible';

import styles from '../../../../Navigation.scss';

const createSecondaryNavigationId = createUniqueIDFactory(
'SecondaryNavigation',
);

interface SecondaryProps {
expanded: boolean;
children?: React.ReactNode;
}

export function Secondary({children, expanded}: SecondaryProps) {
const secondaryNavigationId = createSecondaryNavigationId();
const id = useUniqueId('SecondaryNavigation');
return (
<Collapsible id={secondaryNavigationId} open={expanded}>
<Collapsible id={id} open={expanded}>
<ul className={styles.List}>{children}</ul>
</Collapsible>
);
Expand Down
18 changes: 6 additions & 12 deletions src/components/OptionList/OptionList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, {useState, useRef, useCallback} from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';
import React, {useState, useCallback} from 'react';

import {arraysAreEqual} from '../../utilities/arrays';
import {IconProps} from '../../types';
import {AvatarProps} from '../Avatar';
import {ThumbnailProps} from '../Thumbnail';
import {useUniqueId} from '../../utilities/unique-id';
import {useDeepEffect} from '../../utilities/use-deep-effect';

import {Option} from './components';
Expand Down Expand Up @@ -34,8 +34,6 @@ export interface SectionDescriptor {

type Descriptor = OptionDescriptor | SectionDescriptor;

const getUniqueId = createUniqueIDFactory('OptionList');

export interface OptionListProps {
/** A unique identifier for the option list */
id?: string;
Expand Down Expand Up @@ -66,16 +64,12 @@ export function OptionList({
role,
optionRole,
onChange,
id: propId,
id: idProp,
}: OptionListProps) {
const [normalizedOptions, setNormalizedOptions] = useState(
createNormalizedOptions(options, sections, title),
);
const id = useRef(propId || getUniqueId());

if (id.current !== propId) {
id.current = propId || id.current;
}
const id = useUniqueId('OptionList', idProp);

useDeepEffect(
() => {
Expand Down Expand Up @@ -122,7 +116,7 @@ export function OptionList({
options.map((option, optionIndex) => {
const isSelected = selected.includes(option.value);
const optionId =
option.id || `${id.current}-${sectionIndex}-${optionIndex}`;
option.id || `${id}-${sectionIndex}-${optionIndex}`;

return (
<Option
Expand All @@ -144,7 +138,7 @@ export function OptionList({
{titleMarkup}
<ul
className={styles.Options}
id={`${id.current}-${sectionIndex}`}
id={`${id}-${sectionIndex}`}
role={role}
aria-multiselectable={allowMultiple}
>
Expand Down
8 changes: 4 additions & 4 deletions src/components/OptionList/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import {TickSmallMinor} from '@shopify/polaris-icons';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';
import {classNames} from '../../../../utilities/css';
import {useUniqueId} from '../../../../utilities/unique-id';
import {Icon} from '../../../Icon';

import styles from './Checkbox.scss';
Expand All @@ -17,10 +17,8 @@ export interface CheckboxProps {
onChange(): void;
}

const getUniqueID = createUniqueIDFactory('Checkbox');

export function Checkbox({
id = getUniqueID(),
id: idProp,
checked = false,
disabled,
active,
Expand All @@ -29,6 +27,8 @@ export function Checkbox({
value,
role,
}: CheckboxProps) {
const id = useUniqueId('Checkbox', idProp);

const className = classNames(styles.Checkbox, active && styles.active);
return (
<div className={className}>
Expand Down
27 changes: 18 additions & 9 deletions src/components/PolarisTestProvider/PolarisTestProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import {
import {AppBridgeContext, AppBridgeOptions} from '../../utilities/app-bridge';
import {I18n, I18nContext, TranslationDictionary} from '../../utilities/i18n';
import {LinkContext, LinkLikeComponent} from '../../utilities/link';
import {
UniqueIdFactory,
UniqueIdFactoryContext,
globalIdGeneratorFactory,
} from '../../utilities/unique-id';

type FrameContextType = NonNullable<React.ContextType<typeof FrameContext>>;

Expand Down Expand Up @@ -53,6 +58,8 @@ export function PolarisTestProvider({

const stickyManager = new StickyManager();

const uniqueIdFactory = new UniqueIdFactory(globalIdGeneratorFactory);

// This typing is odd, but as appBridge is deprecated and going away in v5
// I'm not that worried about it
const appBridgeApp = appBridge as React.ContextType<typeof AppBridgeContext>;
Expand All @@ -66,15 +73,17 @@ export function PolarisTestProvider({
<I18nContext.Provider value={intl}>
<ScrollLockManagerContext.Provider value={scrollLockManager}>
<StickyManagerContext.Provider value={stickyManager}>
<AppBridgeContext.Provider value={appBridgeApp}>
<LinkContext.Provider value={link}>
<ThemeContext.Provider value={mergedTheme}>
<FrameContext.Provider value={mergedFrame}>
{children}
</FrameContext.Provider>
</ThemeContext.Provider>
</LinkContext.Provider>
</AppBridgeContext.Provider>
<UniqueIdFactoryContext.Provider value={uniqueIdFactory}>
<AppBridgeContext.Provider value={appBridgeApp}>
<LinkContext.Provider value={link}>
<ThemeContext.Provider value={mergedTheme}>
<FrameContext.Provider value={mergedFrame}>
{children}
</FrameContext.Provider>
</ThemeContext.Provider>
</LinkContext.Provider>
</AppBridgeContext.Provider>
</UniqueIdFactoryContext.Provider>
</StickyManagerContext.Provider>
</ScrollLockManagerContext.Provider>
</I18nContext.Provider>
Expand Down
11 changes: 6 additions & 5 deletions src/components/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';
import {useUniqueId} from '../../utilities/unique-id';
import {Choice, helpTextID} from '../Choice';
import styles from './RadioButton.scss';

Expand Down Expand Up @@ -32,8 +32,6 @@ export interface BaseProps {

export interface RadioButtonProps extends BaseProps {}

const getUniqueID = createUniqueIDFactory('RadioButton');

export function RadioButton({
ariaDescribedBy: ariaDescribedByProp,
label,
Expand All @@ -44,10 +42,13 @@ export function RadioButton({
onChange,
onFocus,
onBlur,
id = getUniqueID(),
name = id,
id: idProp,
name: nameProp,
value,
}: RadioButtonProps) {
const id = useUniqueId('RadioButton', idProp);
const name = nameProp || id;

function handleChange({currentTarget}: React.ChangeEvent<HTMLInputElement>) {
onChange && onChange(currentTarget.checked, id);
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/Scrollable/components/ScrollTo/ScrollTo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {useContext, useEffect, useRef} from 'react';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';
import {useUniqueId} from '../../../../utilities/unique-id';
import {ScrollableContext} from '../../context';

export function ScrollTo() {
Expand All @@ -14,7 +14,7 @@ export function ScrollTo() {
scrollToPosition(anchorNode.current.offsetTop);
}, [scrollToPosition]);

const getUniqueId = createUniqueIDFactory(`ScrollTo`);
const id = useUniqueId(`ScrollTo`);
// eslint-disable-next-line jsx-a11y/anchor-is-valid
return <a id={getUniqueId()} ref={anchorNode} />;
return <a id={id} ref={anchorNode} />;
}
8 changes: 4 additions & 4 deletions src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import {ArrowUpDownMinor} from '@shopify/polaris-icons';
import {createUniqueIDFactory} from '@shopify/javascript-utilities/other';

import {classNames} from '../../utilities/css';
import {useUniqueId} from '../../utilities/unique-id';
import {Labelled, Action, helpTextID} from '../Labelled';
import {Icon} from '../Icon';
import {Error} from '../../types';
Expand Down Expand Up @@ -72,7 +71,6 @@ export interface BaseProps {
export interface SelectProps extends BaseProps {}

const PLACEHOLDER_VALUE = '';
const getUniqueID = createUniqueIDFactory('Select');

export function Select({
options: optionsProp,
Expand All @@ -83,14 +81,16 @@ export function Select({
disabled,
helpText,
placeholder,
id = getUniqueID(),
id: idProp,
name,
value = PLACEHOLDER_VALUE,
error,
onChange,
onFocus,
onBlur,
}: SelectProps) {
const id = useUniqueId('Select', idProp);

const labelHidden = labelInline ? true : labelHiddenProp;

const className = classNames(
Expand Down