Skip to content

Commit

Permalink
[FEC-139] Refactor React components "defaultProps" management (#3676)
Browse files Browse the repository at this point in the history
* refactor(application-components): update components default props management

* chore: add changeset

* refactor: apply review feedback
  • Loading branch information
CarlosCortizasCT authored Dec 13, 2024
1 parent 3bcdba5 commit 9504631
Show file tree
Hide file tree
Showing 30 changed files with 309 additions and 367 deletions.
36 changes: 36 additions & 0 deletions .changeset/four-zebras-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
'@commercetools-frontend/application-components': patch
'@commercetools-frontend/react-notifications': patch
'@commercetools-frontend/application-shell': patch
'@commercetools-frontend/permissions': patch
'@commercetools-frontend/sdk': patch
---

As part of the preparations for the upcoming update to the newest React version, we have updated how we manage components default properties as our current implementation will no longer be supported ([reference](https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-proptypes-and-defaultprops)).

```ts
// BEFORE
type TMyComponentProps = {
message: string;
size: string;
}

function MyComponent(props: TMyComponentProps) {
...
}

MyComponent.defaultProps = {
size: 'big'
}


// AFTER
type TMyComponentProps = {
message: string;
size?: string; // <--- Note this property is now defined as optional
}

function MyComponent({ size = 'big', ...props }: TMyComponentProps) {
...
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,10 @@ type CustomFormDetailPageProps = {
) => void;
};

const defaultProps: Pick<CustomFormDetailPageProps, 'hideControls'> = {
hideControls: false,
};

const CustomFormDetailPage = (props: CustomFormDetailPageProps) => {
const CustomFormDetailPage = ({
hideControls = false,
...props
}: CustomFormDetailPageProps) => {
warning(
props.title !== undefined || props.customTitleRow !== undefined,
'DetailPage: one of either `title` or `customTitleRow` is required but both their values are `undefined`'
Expand All @@ -110,11 +109,11 @@ const CustomFormDetailPage = (props: CustomFormDetailPageProps) => {
)}
<CustomViewsSelector
margin={getCustomViewsSelectorMargin(
!props.hideControls && !!props.formControls
!hideControls && !!props.formControls
)}
customViewLocatorCode={props.customViewLocatorCode}
/>
{!props.hideControls && props.formControls && (
{!hideControls && props.formControls && (
<HeaderControlsWrapper>
<Spacings.Inline justifyContent="flex-end">
{props.formControls}
Expand All @@ -127,7 +126,6 @@ const CustomFormDetailPage = (props: CustomFormDetailPageProps) => {
);
};
CustomFormDetailPage.displayName = 'CustomFormDetailPage';
CustomFormDetailPage.defaultProps = defaultProps;
// Static export of pre-configured page header title component to easily
// use as part of a custom title row
CustomFormDetailPage.PageHeaderTitle = PageHeaderTitle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,25 @@ type FormDetailPageProps = {
/**
* Hides the form controls.
*/
hideControls: boolean;
hideControls?: boolean;
/**
* The Icon for the secondary button label
*/
iconLeftSecondaryButton?: ReactElement;
};

const defaultProps: Pick<FormDetailPageProps, 'hideControls'> = {
hideControls: false,
};

const FormDetailPage = (props: FormDetailPageProps) => (
const FormDetailPage = ({
hideControls = false,
...props
}: FormDetailPageProps) => (
<CustomFormDetailPage
title={props.title}
subtitle={props.subtitle}
customTitleRow={props.customTitleRow}
customViewLocatorCode={props.customViewLocatorCode}
previousPathLabel={props.previousPathLabel}
onPreviousPathClick={props.onPreviousPathClick}
hideControls={props.hideControls}
hideControls={hideControls}
formControls={
<>
<CustomFormDetailPage.FormSecondaryButton
Expand All @@ -129,7 +128,6 @@ const FormDetailPage = (props: FormDetailPageProps) => (
</CustomFormDetailPage>
);
FormDetailPage.displayName = 'FormDetailPage';
FormDetailPage.defaultProps = defaultProps;
// This is a convenience proxy export to expose pre-defined Intl messages defined in the `@commercetools-frontend/i18n` package.
// The Intl messages can be used for button labels.
// Static export of pre-configured page header title component to easily
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type TTabularDetailPageProps = {
/**
* Determines if the form controls should be rendered.
*/
hideControls: boolean;
hideControls?: boolean;
/**
* These codes are used to configure which Custom Views are available for every tab.
*/
Expand All @@ -78,11 +78,10 @@ type TTabularDetailPageProps = {
) => void;
};

const defaultProps: Pick<TTabularDetailPageProps, 'hideControls'> = {
hideControls: false,
};

const TabularDetailPage = (props: TTabularDetailPageProps) => {
const TabularDetailPage = ({
hideControls = false,
...props
}: TTabularDetailPageProps) => {
const { currentCustomViewLocatorCode } = useCustomViewLocatorSelector(
props.customViewLocatorCodes
);
Expand Down Expand Up @@ -111,7 +110,7 @@ const TabularDetailPage = (props: TTabularDetailPageProps) => {
tabControls={props.tabControls}
formControls={
<FormControlsContainer>
{!props.hideControls && props.formControls && (
{!hideControls && props.formControls && (
<Spacings.Inline alignItems="flex-end">
{props.formControls}
</Spacings.Inline>
Expand All @@ -131,7 +130,6 @@ const TabularDetailPage = (props: TTabularDetailPageProps) => {
);
};
TabularDetailPage.displayName = 'TabularDetailPage';
TabularDetailPage.defaultProps = defaultProps;
// Static export of pre-configured form control buttons to easily re-use
// them in the custom controls.
TabularDetailPage.FormPrimaryButton = FormPrimaryButton;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,21 @@ export type TConfirmationDialogProps = {
size?: 'm' | 'l' | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 'scale';
zIndex?: number;
children: ReactNode;
labelSecondary: Label;
labelPrimary: Label;
labelSecondary?: Label;
labelPrimary?: Label;
isPrimaryButtonDisabled?: boolean;
onCancel: (event: SyntheticEvent) => void;
onConfirm: (event: SyntheticEvent) => void;
dataAttributesSecondaryButton?: { [key: string]: string };
dataAttributesPrimaryButton?: { [key: string]: string };
getParentSelector?: () => HTMLElement;
};
const defaultProps: Pick<
TConfirmationDialogProps,
'labelSecondary' | 'labelPrimary'
> = {
labelSecondary: sharedMessages.cancel,
labelPrimary: sharedMessages.confirm,
};

const ConfirmationDialog = (props: TConfirmationDialogProps) => (
const ConfirmationDialog = ({
labelSecondary = sharedMessages.cancel,
labelPrimary = sharedMessages.confirm,
...props
}: TConfirmationDialogProps) => (
<DialogContainer
isOpen={props.isOpen}
onClose={props.onClose}
Expand All @@ -52,8 +49,8 @@ const ConfirmationDialog = (props: TConfirmationDialogProps) => (
<DialogHeader title={props.title} onClose={props.onClose} />
<DialogContent>{props.children}</DialogContent>
<DialogFooter
labelSecondary={props.labelSecondary}
labelPrimary={props.labelPrimary}
labelSecondary={labelSecondary}
labelPrimary={labelPrimary}
isPrimaryButtonDisabled={props.isPrimaryButtonDisabled}
onCancel={props.onCancel}
onConfirm={props.onConfirm}
Expand All @@ -63,7 +60,6 @@ const ConfirmationDialog = (props: TConfirmationDialogProps) => (
</DialogContainer>
);
ConfirmationDialog.displayName = 'ConfirmationDialog';
ConfirmationDialog.defaultProps = defaultProps;
// This is a convenience proxy export to expose pre-defined Intl messages defined in the `@commercetools-frontend/i18n` package.
// The Intl messages can be used for button labels.
ConfirmationDialog.Intl = sharedMessages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export type TFormDialogProps = {
size?: 'm' | 'l' | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 'scale';
zIndex?: number;
children: ReactNode;
labelSecondary: Label;
labelPrimary: Label;
labelSecondary?: Label;
labelPrimary?: Label;
isPrimaryButtonDisabled?: boolean;
onSecondaryButtonClick: (event: SyntheticEvent) => void;
onPrimaryButtonClick: (event: SyntheticEvent) => void;
Expand All @@ -33,13 +33,12 @@ export type TFormDialogProps = {
iconLeftSecondaryButton?: ReactElement;
footerContent?: ReactNode;
};
const defaultProps: Pick<TFormDialogProps, 'labelSecondary' | 'labelPrimary'> =
{
labelSecondary: sharedMessages.cancel,
labelPrimary: sharedMessages.save,
};

const FormDialog = (props: TFormDialogProps) => (
const FormDialog = ({
labelSecondary = sharedMessages.cancel,
labelPrimary = sharedMessages.save,
...props
}: TFormDialogProps) => (
<DialogContainer
isOpen={props.isOpen}
onClose={props.onClose}
Expand All @@ -52,8 +51,8 @@ const FormDialog = (props: TFormDialogProps) => (
<DialogHeader title={props.title} onClose={props.onClose} />
<DialogContent>{props.children}</DialogContent>
<DialogFooter
labelSecondary={props.labelSecondary}
labelPrimary={props.labelPrimary}
labelSecondary={labelSecondary}
labelPrimary={labelPrimary}
isPrimaryButtonDisabled={props.isPrimaryButtonDisabled}
onCancel={props.onSecondaryButtonClick}
onConfirm={props.onPrimaryButtonClick}
Expand All @@ -65,7 +64,6 @@ const FormDialog = (props: TFormDialogProps) => (
</DialogContainer>
);
FormDialog.displayName = 'FormDialog';
FormDialog.defaultProps = defaultProps;
// This is a convenience proxy export to expose pre-defined Intl messages defined in the `@commercetools-frontend/i18n` package.
// The Intl messages can be used for button labels.
FormDialog.Intl = sharedMessages;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,12 @@ const getOverlayElement: ModalProps['overlayElement'] = (
type Props = {
isOpen: boolean;
onClose?: (event: SyntheticEvent) => void;
size: 'm' | 'l' | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 'scale';
size?: 'm' | 'l' | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 'scale';
zIndex?: number;
title: ReactNode;
'aria-label'?: string;
children: ReactNode;
getParentSelector: typeof getDefaultParentSelector;
};
const defaultProps: Pick<Props, 'size' | 'getParentSelector'> = {
// TODO: t-shirt sizes are deprecated but we need to keep using them for
// backwards compatibility and to help with styling migration
// After the migration is done, we should change this default value to 13.
// t-shirt sizes then can be removed in a next breaking change release
size: 'l',
getParentSelector: getDefaultParentSelector,
getParentSelector?: typeof getDefaultParentSelector;
};

type GridAreaProps = {
Expand All @@ -65,7 +57,11 @@ const GridArea = styled.div<GridAreaProps>`
grid-area: ${(props) => props.name};
`;

const DialogContainer = (props: Props) => {
const DialogContainer = ({
size = 13,
getParentSelector = getDefaultParentSelector,
...props
}: Props) => {
useWarning(
typeof props.title === 'string' ||
(typeof props.title !== 'string' && Boolean(props['aria-label'])),
Expand All @@ -81,12 +77,12 @@ const DialogContainer = (props: Props) => {
shouldCloseOnOverlayClick={Boolean(props.onClose)}
shouldCloseOnEsc={Boolean(props.onClose)}
overlayElement={getOverlayElement}
overlayClassName={makeClassName(getOverlayStyles(props))}
className={makeClassName(getModalContentStyles(props))}
overlayClassName={makeClassName(getOverlayStyles({ size, ...props }))}
className={makeClassName(getModalContentStyles({ size, ...props }))}
contentLabel={
typeof props.title === 'string' ? props.title : props['aria-label']
}
parentSelector={props.getParentSelector}
parentSelector={getParentSelector}
ariaHideApp={false}
>
<GridArea name="top" />
Expand Down Expand Up @@ -140,6 +136,5 @@ const DialogContainer = (props: Props) => {
);
};
DialogContainer.displayName = 'DialogContainer';
DialogContainer.defaultProps = defaultProps;

export default DialogContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,23 @@ type Props = {
labelPrimary: Label;
onCancel: (event: SyntheticEvent) => void;
onConfirm: (event: SyntheticEvent) => void;
isPrimaryButtonDisabled: boolean;
dataAttributesPrimaryButton: { [key: string]: string };
dataAttributesSecondaryButton: { [key: string]: string };
isPrimaryButtonDisabled?: boolean;
dataAttributesPrimaryButton?: { [key: string]: string };
dataAttributesSecondaryButton?: { [key: string]: string };
children?: never;
iconLeftSecondaryButton?: ReactElement;
footerContent?: ReactNode;
};
const defaultProps: Pick<
Props,
| 'isPrimaryButtonDisabled'
| 'dataAttributesPrimaryButton'
| 'dataAttributesSecondaryButton'
> = {
isPrimaryButtonDisabled: false,
dataAttributesPrimaryButton: {},
dataAttributesSecondaryButton: {},
};

const getFormattedLabel = (label: Label, intl: IntlShape) =>
typeof label === 'string' ? label : intl.formatMessage(label);

const DialogFooter = (props: Props) => {
const DialogFooter = ({
isPrimaryButtonDisabled = false,
dataAttributesPrimaryButton = {},
dataAttributesSecondaryButton = {},
...props
}: Props) => {
const intl = useIntl();
return (
<div
Expand All @@ -61,20 +56,19 @@ const DialogFooter = (props: Props) => {
label={getFormattedLabel(props.labelSecondary, intl)}
onClick={props.onCancel}
iconLeft={props.iconLeftSecondaryButton}
{...filterDataAttributes(props.dataAttributesSecondaryButton)}
{...filterDataAttributes(dataAttributesSecondaryButton)}
/>
<PrimaryButton
label={getFormattedLabel(props.labelPrimary, intl)}
onClick={props.onConfirm}
isDisabled={props.isPrimaryButtonDisabled}
{...filterDataAttributes(props.dataAttributesPrimaryButton)}
isDisabled={isPrimaryButtonDisabled}
{...filterDataAttributes(dataAttributesPrimaryButton)}
/>
</Spacings.Inline>
</Spacings.Inline>
</div>
);
};
DialogFooter.displayName = 'DialogFooter';
DialogFooter.defaultProps = defaultProps;

export default DialogFooter;
Loading

0 comments on commit 9504631

Please sign in to comment.