From 1d32cafe6389f813ba25ce793cc6bd7e6e1ca794 Mon Sep 17 00:00:00 2001 From: JeremyRH Date: Fri, 9 Jan 2026 12:28:27 -0800 Subject: [PATCH] feat: remove proptypes from components and use server-safe getuniqueid for id generation --- .eslintrc.json | 3 +- package.json | 2 - src/components/Address/CountryInput.tsx | 11 -- src/components/Address/StateInput.tsx | 12 -- src/components/Callout/Callout.tsx | 1 + src/components/Carousel/ImageCarousel.js | 15 -- .../Carousel/UncontrolledCarousel.js | 14 -- .../Checkbox/CheckboxBooleanInput.tsx | 85 ++++------- src/components/Checkbox/CheckboxListInput.tsx | 8 - .../CollapsableText/CollapsableText.tsx | 3 +- src/components/FilterList/FilterList.js | 8 - src/components/Form/BoundForm.js | 14 +- src/components/Form/Form.stories.js | 138 ++++++++++-------- src/components/Form/FormChoice.js | 20 +-- src/components/Form/FormRow.spec.tsx | 1 - src/components/Form/FormRow.stories.js | 2 - src/components/HasManyFields/HasManyFields.js | 19 --- .../HasManyFields/HasManyFieldsAdd.js | 7 - .../HasManyFields/HasManyFieldsRow.tsx | 8 +- src/components/HelpBubble/HelpBubble.tsx | 16 +- src/components/Icon/FontAwesomeAPM.tsx | 119 --------------- src/components/Icon/Icon.tsx | 121 ++++++++++++++- .../Input/CreditCardNumber.stories.js | 1 - src/components/Input/DateInput.js | 29 ---- src/components/Input/MonthInput.js | 23 --- src/components/Input/PatternInput.js | 9 -- src/components/Input/TimeInput.js | 19 --- src/components/Input/TimeInput.spec.js | 4 - src/components/List/List.tsx | 30 +--- src/components/List/ListItem.tsx | 4 +- src/components/List/SortableList.tsx | 8 +- .../List/components/FilterHeader.tsx | 10 +- src/components/List/components/SortHeader.tsx | 14 +- src/components/Note/EditableNoteMentions.tsx | 1 + src/components/Note/NoteMentions.tsx | 1 + src/components/Note/Notes.tsx | 1 + src/components/Pagination/Paginator.tsx | 21 --- src/components/Pagination/components/Page.tsx | 7 - .../Pagination/components/ShortcutLink.tsx | 9 -- .../Pagination/components/Summary.tsx | 9 -- src/components/Select/Select.js | 5 - src/components/Select/SelectMultiValue.js | 11 -- src/components/Select/SelectOption.js | 14 -- src/components/Status/Status.tsx | 6 +- src/components/Table/SortableTable.js | 70 ++------- src/components/Table/UncontrolledTable.js | 18 --- src/components/Tooltip/TooltipButton.tsx | 10 +- src/components/Tree/TreeItem.tsx | 6 +- .../TruncatedText/TruncatedText.tsx | 7 +- src/util/bound.js | 12 +- src/util/typeHelpers.ts | 27 ---- src/util/uniqueId.ts | 8 + yarn.lock | 20 --- 53 files changed, 288 insertions(+), 753 deletions(-) delete mode 100644 src/components/Icon/FontAwesomeAPM.tsx create mode 100644 src/util/uniqueId.ts diff --git a/.eslintrc.json b/.eslintrc.json index 4df0b234a..41309169f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -42,11 +42,10 @@ "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." } ], - "react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }], "react/no-array-index-key": "error", "react/no-find-dom-node": "error", "react/prefer-stateless-function": "off", - "react/prop-types": "off", // This will be unnecessary once everything is a function component + "react/prop-types": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "require-jsdoc": "off" diff --git a/package.json b/package.json index a77fe342d..72a3dcba3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "classnames": "^2.2.6", "credit-card-type": "^5.0.1", "date-fns": "^1.30.1", - "deprecated-prop-type": "^1.0.0", "fast-deep-equal": "^3.1.3", "fecha": "^2.3.3", "imask": "^6.2.2", @@ -65,7 +64,6 @@ "lodash.uniqueid": "^4.0.1", "lodash.without": "^4.4.0", "memoize-one": "^5.1.1", - "prop-types": "^15.7.2", "react-imask": "^6.2.2", "react-resize-detector": "^4.2.3", "react-select-plus": "1.2.0", diff --git a/src/components/Address/CountryInput.tsx b/src/components/Address/CountryInput.tsx index 51993851d..7ca9e83c9 100644 --- a/src/components/Address/CountryInput.tsx +++ b/src/components/Address/CountryInput.tsx @@ -1,5 +1,4 @@ import classnames from 'classnames'; -import PropTypes from 'prop-types'; import React, { FC } from 'react'; import { InputProps } from 'reactstrap'; import Input from '../Input/Input'; @@ -43,16 +42,6 @@ const CountryInput: FC = ({ ); }; -CountryInput.propTypes = { - className: PropTypes.string, - disabled: PropTypes.bool, - id: PropTypes.string, - name: PropTypes.string, - onChange: PropTypes.func, - placeholder: PropTypes.string, - value: PropTypes.string, -}; - CountryInput.defaultProps = defaultProps; CountryInput.displayName = 'CountryInput'; diff --git a/src/components/Address/StateInput.tsx b/src/components/Address/StateInput.tsx index efedcbebc..6b6c1b58e 100644 --- a/src/components/Address/StateInput.tsx +++ b/src/components/Address/StateInput.tsx @@ -1,5 +1,4 @@ import classnames from 'classnames'; -import PropTypes from 'prop-types'; import React, { FC } from 'react'; import { InputProps } from 'reactstrap'; import Input from '../Input/Input'; @@ -68,17 +67,6 @@ const StateInput: FC = ({ ); }; -StateInput.propTypes = { - className: PropTypes.string, - countries: PropTypes.arrayOf(PropTypes.string.isRequired), - disabled: PropTypes.bool, - id: PropTypes.string, - name: PropTypes.string, - onChange: PropTypes.func, - placeholder: PropTypes.string, - value: PropTypes.string, -}; - StateInput.defaultProps = defaultProps; StateInput.displayName = 'StateInput'; diff --git a/src/components/Callout/Callout.tsx b/src/components/Callout/Callout.tsx index 677ad1210..eb15f0400 100644 --- a/src/components/Callout/Callout.tsx +++ b/src/components/Callout/Callout.tsx @@ -61,6 +61,7 @@ Callout.defaultProps = { className: '', color: 'primary', background: 'light', + // eslint-disable-next-line react/default-props-match-prop-types placement: 'bottom', }; diff --git a/src/components/Carousel/ImageCarousel.js b/src/components/Carousel/ImageCarousel.js index b7ccff504..c5af11446 100644 --- a/src/components/Carousel/ImageCarousel.js +++ b/src/components/Carousel/ImageCarousel.js @@ -1,25 +1,10 @@ -/* eslint-disable react/default-props-match-prop-types */ -// Enable the above rule when the following is addressed https://github.com/yannickcr/eslint-plugin-react/issues/1674 - import classnames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import Icon from '../Icon/Icon'; import Modal from '../Modal/Modal'; import UncontrolledCarousel from './UncontrolledCarousel'; export default class ImageCarousel extends React.Component { - static propTypes = { - items: PropTypes.array.isRequired, - indicators: PropTypes.bool, - controls: PropTypes.bool, - autoPlay: PropTypes.bool, - defaultActiveIndex: PropTypes.number, - activeIndex: PropTypes.number, - slide: PropTypes.bool, - ...Modal.propTypes, - }; - static defaultProps = { autoPlay: false, backdrop: true, diff --git a/src/components/Carousel/UncontrolledCarousel.js b/src/components/Carousel/UncontrolledCarousel.js index e6e8713a0..ababbaac9 100644 --- a/src/components/Carousel/UncontrolledCarousel.js +++ b/src/components/Carousel/UncontrolledCarousel.js @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React, { Component } from 'react'; import Carousel from './Carousel'; import CarouselCaption from './CarouselCaption'; @@ -6,18 +5,6 @@ import CarouselControl from './CarouselControl'; import CarouselIndicators from './CarouselIndicators'; import CarouselItem from './CarouselItem'; -const propTypes = { - items: PropTypes.array.isRequired, - indicators: PropTypes.bool, - controls: PropTypes.bool, - autoPlay: PropTypes.bool, - defaultActiveIndex: PropTypes.number, - activeIndex: PropTypes.number, - next: PropTypes.func, - previous: PropTypes.func, - goToIndex: PropTypes.func, -}; - class UncontrolledCarousel extends Component { constructor(props) { super(props); @@ -123,7 +110,6 @@ class UncontrolledCarousel extends Component { } } -UncontrolledCarousel.propTypes = propTypes; UncontrolledCarousel.defaultProps = { controls: true, indicators: true, diff --git a/src/components/Checkbox/CheckboxBooleanInput.tsx b/src/components/Checkbox/CheckboxBooleanInput.tsx index 2bdf6bc6e..28093f409 100644 --- a/src/components/Checkbox/CheckboxBooleanInput.tsx +++ b/src/components/Checkbox/CheckboxBooleanInput.tsx @@ -1,66 +1,45 @@ import classnames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useState } from 'react'; import type { InputProps } from 'reactstrap'; +import { getUniqueId } from '../../util/uniqueId'; import FormGroup from '../Form/FormGroup'; import Input from '../Input/Input'; import Label from '../Label/Label'; -interface CheckboxBooleanInputSpecificProps { +export type CheckboxBooleanInputProps = Omit & { checkboxLabel?: React.ReactNode; onChange?: (isChecked: boolean) => void; value?: boolean; +}; + +function CheckboxBooleanInput({ + checkboxLabel, + className, + onChange, + value, + ...inputProps +}: CheckboxBooleanInputProps) { + const [generatedId] = useState(() => getUniqueId('checkbox-boolean-input-')); + const id = inputProps.id || generatedId; + const classNames = classnames('pt-2', className); + return ( + + onChange && onChange(e.target.checked)} + /> + {checkboxLabel && ( + + )} + + ); } -type ExtendsWithTypeOverrides = U & Omit; -export type CheckboxBooleanInputProps = ExtendsWithTypeOverrides< - InputProps, - CheckboxBooleanInputSpecificProps ->; -let count = 0; - -function getID() { - return `checkbox-boolean-input-${count++}`; -} - -class CheckboxBooleanInput extends React.Component { - static propTypes = { - id: PropTypes.string, - checkboxLabel: PropTypes.node, - className: PropTypes.string, - onChange: PropTypes.func, - value: PropTypes.bool, - }; - - id = getID(); - - constructor(props: CheckboxBooleanInputProps) { - super(props); - - this.id = props.id || this.id; - } - - render() { - const { checkboxLabel, className, onChange, value, ...inputProps } = this.props; - const classNames = classnames('pt-2', className); - - return ( - - onChange && onChange(e.target.checked)} - /> - {checkboxLabel && ( - - )} - - ); - } -} +CheckboxBooleanInput.displayName = 'CheckboxBooleanInput'; export default CheckboxBooleanInput; diff --git a/src/components/Checkbox/CheckboxListInput.tsx b/src/components/Checkbox/CheckboxListInput.tsx index 991a1dcc2..791028e41 100644 --- a/src/components/Checkbox/CheckboxListInput.tsx +++ b/src/components/Checkbox/CheckboxListInput.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; export interface CheckboxListInputProps { @@ -9,13 +8,6 @@ export interface CheckboxListInputProps { } class CheckboxListInput extends React.Component { - static propTypes = { - children: PropTypes.node, - className: PropTypes.string, - onChange: PropTypes.func, - value: PropTypes.array, - }; - onChange = (e: React.ChangeEvent) => { if (this.props.onChange) { const { checked, value } = e.target; diff --git a/src/components/CollapsableText/CollapsableText.tsx b/src/components/CollapsableText/CollapsableText.tsx index 1ea405831..6fe4e5ef5 100644 --- a/src/components/CollapsableText/CollapsableText.tsx +++ b/src/components/CollapsableText/CollapsableText.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-unused-prop-types */ import classNames from 'classnames'; import React, { useState, useRef } from 'react'; import { useIntervalRef } from '../../hooks/useIntervalRef'; @@ -23,7 +22,7 @@ export interface CollapsableTextProps { collapsed?: boolean; lessLabel?: React.ReactNode; moreLabel?: React.ReactNode; - /** @deprecated maxLength has no effect. Use maxLines instead */ + /** @deprecated maxLength has no effect. Use maxLines instead */ // eslint-disable-next-line react/no-unused-prop-types maxLength?: number; maxLines?: number; alignToggleButton?: AlignToggleButton; diff --git a/src/components/FilterList/FilterList.js b/src/components/FilterList/FilterList.js index 96fff3658..7621d1934 100644 --- a/src/components/FilterList/FilterList.js +++ b/src/components/FilterList/FilterList.js @@ -1,15 +1,7 @@ -import PropTypes from 'prop-types'; import React from 'react'; import LabelBadge from '../LabelBadge/LabelBadge'; export default class FilterList extends React.Component { - static propTypes = { - className: PropTypes.string, - filters: PropTypes.array.isRequired, - maxWidth: PropTypes.number, - onRemove: PropTypes.func, - }; - static defaultProps = { maxWidth: 14, }; diff --git a/src/components/Form/BoundForm.js b/src/components/Form/BoundForm.js index a12d52f0d..c73f716cb 100644 --- a/src/components/Form/BoundForm.js +++ b/src/components/Form/BoundForm.js @@ -1,16 +1,8 @@ import noop from 'lodash.noop'; -import PropTypes from 'prop-types'; import React from 'react'; import Form from './Form'; class BoundForm extends React.Component { - static propTypes = { - errors: PropTypes.object, - object: PropTypes.object.isRequired, - onSubmit: PropTypes.func, - onChange: PropTypes.func, - }; - static defaultProps = { errors: {}, onSubmit: noop, @@ -18,9 +10,9 @@ class BoundForm extends React.Component { }; static childContextTypes = { - value: PropTypes.object, - errors: PropTypes.object, - onChange: PropTypes.func, + value: noop, + errors: noop, + onChange: noop, }; constructor(props) { diff --git a/src/components/Form/Form.stories.js b/src/components/Form/Form.stories.js index 9a136cfaa..bb4eac743 100644 --- a/src/components/Form/Form.stories.js +++ b/src/components/Form/Form.stories.js @@ -42,7 +42,7 @@ export default { export const Inputs = (args) => (
- +

See all supported Input types here:{' '} @@ -52,31 +52,31 @@ export const Inputs = (args) => (


- + - + - + - + - + - + - + - + - +
); @@ -107,79 +107,96 @@ export const FloatingLabels = (args) => ( export const FormRows = (args) => (
- - - - - - - - - A New Hope - The Empire Strikes Back - The Force Awakens - + + + + + + + - Death Star - Millennium Falcon - Imperial Shuttle + A New Hope + The Empire Strikes Back + The Force Awakens - - Darth Vader - Luke Skywalker - Emperor Palpatine - Rey - TK-421 + + + Death Star + + + Millennium Falcon + + + Imperial Shuttle + - - + + Darth Vader + Luke Skywalker + + Emperor Palpatine + + Rey + TK-421 + + + Yes No - + ); export const Bound = (args) => ( - - + + - - A New Hope - The Empire Strikes Back - The Force Awakens + + A New Hope + The Empire Strikes Back + The Force Awakens - - Darth Vader - Luke Skywalker - Emperor Palpatine - Rey - TK-421 + + Darth Vader + Luke Skywalker + + Emperor Palpatine + + Rey + TK-421 - - + + Death Star Millennium Falcon Imperial Shuttle - + ( ); + Bound.args = { errors: { lastName: 'Last Name is required' }, onSubmit: action('submit'), diff --git a/src/components/Form/FormChoice.js b/src/components/Form/FormChoice.js index 19db89b87..974126a07 100644 --- a/src/components/Form/FormChoice.js +++ b/src/components/Form/FormChoice.js @@ -1,30 +1,14 @@ import classname from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; +import { getUniqueId } from '../../util/uniqueId'; import Input from '../Input/Input'; import Label from '../Label/Label'; import FormGroup from './FormGroup'; -let count = 0; - -function getID() { - return `form-choice-${count++}`; -} - class FormChoice extends React.Component { - static propTypes = { - inline: PropTypes.bool, - disabled: PropTypes.bool, - children: PropTypes.node, - containerClassName: PropTypes.string, - id: PropTypes.string, - type: PropTypes.oneOf(['checkbox', 'radio', 'select']), - value: PropTypes.any, - }; - constructor(props) { super(props); - this.id = props.id || getID(); + this.id = props.id || getUniqueId('form-choice-'); } render() { diff --git a/src/components/Form/FormRow.spec.tsx b/src/components/Form/FormRow.spec.tsx index 4c0a5b67e..025584872 100644 --- a/src/components/Form/FormRow.spec.tsx +++ b/src/components/Form/FormRow.spec.tsx @@ -75,7 +75,6 @@ describe('', () => { {props.invalid !== undefined && 'invalid!'} ); - Custom2.propTypes = {}; it('should omit valid/invalid props', () => { const component2 = render(); diff --git a/src/components/Form/FormRow.stories.js b/src/components/Form/FormRow.stories.js index 82448d5a7..90561a2a7 100644 --- a/src/components/Form/FormRow.stories.js +++ b/src/components/Form/FormRow.stories.js @@ -75,7 +75,6 @@ export const WithCheckboxes = ({ label, stacked, inline }) => { const [favorites, setFavorites] = useState(['Bravo']); return ( setFavorites(selection)} @@ -106,7 +105,6 @@ export const WithRadioButtons = ({ label, stacked, inline }) => { const [favorite, setFavorite] = useState('Bravo'); return ( setFavorite(e.target.value)} stacked={stacked} diff --git a/src/components/HasManyFields/HasManyFields.js b/src/components/HasManyFields/HasManyFields.js index 752d5158d..deea2f634 100644 --- a/src/components/HasManyFields/HasManyFields.js +++ b/src/components/HasManyFields/HasManyFields.js @@ -1,5 +1,4 @@ import noop from 'lodash.noop'; -import PropTypes from 'prop-types'; import React from 'react'; import ReactDOM from 'react-dom'; import withDragHandler from '../Reorderable/DragHandler'; @@ -35,24 +34,6 @@ const SortableContainer = ReorderableContainer( ); class HasManyFields extends React.Component { - static propTypes = { - blank: PropTypes.any, - defaultValue: PropTypes.array, - disabled: PropTypes.bool, - deleteProps: PropTypes.object, - errors: PropTypes.array, - label: PropTypes.string.isRequired, - onAdd: PropTypes.func, - onRemove: PropTypes.func, - onUpdate: PropTypes.func, - onChange: PropTypes.func, - template: PropTypes.oneOfType([PropTypes.func, PropTypes.element]).isRequired, - value: PropTypes.array, - minimumRows: PropTypes.number, - maximumRows: PropTypes.number, - reorderable: PropTypes.bool, - }; - static defaultProps = { defaultValue: [], errors: [], diff --git a/src/components/HasManyFields/HasManyFieldsAdd.js b/src/components/HasManyFields/HasManyFieldsAdd.js index 3612270a5..896d888d3 100644 --- a/src/components/HasManyFields/HasManyFieldsAdd.js +++ b/src/components/HasManyFields/HasManyFieldsAdd.js @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import Button from '../Button/Button'; import Icon from '../Icon/Icon'; @@ -15,12 +14,6 @@ const HasManyFieldsAdd = ({ children, className, ...props }) => { ); }; -HasManyFieldsAdd.propTypes = { - className: PropTypes.string, - children: PropTypes.node.isRequired, - disabled: PropTypes.bool, -}; - HasManyFieldsAdd.displayName = 'HasManyFieldsAdd'; export default HasManyFieldsAdd; diff --git a/src/components/HasManyFields/HasManyFieldsRow.tsx b/src/components/HasManyFields/HasManyFieldsRow.tsx index 53acccd62..b6456604c 100644 --- a/src/components/HasManyFields/HasManyFieldsRow.tsx +++ b/src/components/HasManyFields/HasManyFieldsRow.tsx @@ -1,6 +1,7 @@ import { Placement } from '@popperjs/core'; import classnames from 'classnames'; import React, { useState } from 'react'; +import { getUniqueId } from '../../util/uniqueId'; import Button from '../Button/Button'; import ConfirmationButton, { ConfirmationButtonProps } from '../Button/ConfirmationButton'; import Icon from '../Icon/Icon'; @@ -9,11 +10,6 @@ import Row from '../Layout/Row'; import Tooltip from '../Tooltip/Tooltip'; const noop = () => undefined; -let count = 0; - -function getID() { - return `hmf-${(count += 1)}`; // eslint-disable-line no-return-assign -} interface HasManyFieldsRowProps { children: React.ReactNode; @@ -37,7 +33,7 @@ const HasManyFieldsRow = ({ deleteProps, ...props }: HasManyFieldsRowProps) => { - const [id] = useState(getID()); + const [id] = useState(() => getUniqueId('hmf-', 1)); const classNames = classnames('mb-4 gx-0', className); // The `disabled ?