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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pickers] Simplify the typing of the codebase #14823

Open
flaviendelangle opened this issue Oct 4, 2024 · 0 comments
Open

[pickers] Simplify the typing of the codebase #14823

flaviendelangle opened this issue Oct 4, 2024 · 0 comments
Assignees
Labels
component: pickers This is the name of the generic UI component, not the React module! RFC Request For Comments typescript

Comments

@flaviendelangle
Copy link
Member

flaviendelangle commented Oct 4, 2024

Proposals to reduce the generic hell

0. Current state ✔️

Here is what an interface in the @mui/x-date-pickers package can currently look like:

interface SomeInterface<
  TValue, 
  TDate extends PickerValidDate, 
  TSection extends FieldSection, 
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError
> {
  value: TValue;
  referenceDate: TDate;
  sections: TSection[];
  onError: (error: TError) => void;
  enableAccessibleFieldDOMStructure: TEnableAccessibleFieldDOMStructure;
}

The properties of this interface were chosen to keep it simple and use every generic, not to represent any realistic usage.

1. Remove TDate generic in favor of PickerValidDate (v8) ✔️

See #14796 for more context

Here is what the 0. example would look like:

interface SomeInterface<
  TValue, 
  TSection extends FieldSection, 
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError
> {
  value: TValue;
  referenceDate: PickerValidDate;
  sections: TSection[];
  onError: (error: TError) => void;
  someProperty: SomeProperty<TEnableAccessibleFieldDOMStructure>;
}

2. Infer the TSection generic from TValue (v8) ✔️

TSection can be infered from TValue
It equals FieldSection on non range and FieldSection & { rangePosition: 'start' | 'end' } on range

We can remove it and infer it from TValue

Here is what the 1. example would look like (I also added an extend to TValue to make it safer):

interface SomeInterface<
  TValue extends PickerValueValue,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError
> {
  value: TValue;
  referenceDate: PickerValidDate;
  sections: InferFieldSection<TValue>[];
  onError: (error: TError) => void;
  someProperty: SomeProperty<TEnableAccessibleFieldDOMStructure>;
}

3. Add a TManager generic for interfaces that uses the new value manager (v8)

See #14496 (comment) for full context

Some interfaces rely a lot on the following generics:

  • TSection (or nothing if 2. is applied)
  • TError
  • TInternalProps

If we introduce the notion of value manager as described in the link above, we can pass the manager as a generic to replace the 4 generic listed above:

// Before
interface SomeInterface<
  TValue extends PickerValueValue,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError,
  TInternalProps extends {},
  TInternalPropsWithDefaults extends UseFieldInternalProps<
    TValue,
    TEnableAccessibleFieldDOMStructure,
    TError
  >,
  TForwardedProps extends UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>
> {
  valueManager: TManager<
    TValue, 
    TEnableAccessibleFieldDOMStructure,
    TError, 
    TInternalProps, 
    TInternalPropsWithDefaults
  >;
  forwardedProps: TForwardedProps;
  internalProps: TInternalProps;
}

// After
interface SomeInterface<
  TManager extends PickerAnyValueManagerV8,
  TForwardedProps extends UseFieldForwardedProps<
    PickerManagerProperties<TManager>['enableAccessibleFieldDOMStructure']
  >,
> {
  valueManager: TManager;
  forwardedProps: TForwardedProps;
  internalProps: PickerManagerProperties<TManager>['fieldInternalProps'];
}

The gain in generic is massive, but accessing those properties is more verbose (PickerManagerProperties<TManager>['enableAccessibleFieldDOMStructure'] instead of TEnableAccessibleFieldDOMStructure).
I think this is a massive win for interfaces that are widely used across the codebase (the interface itself can be slightly more verbose if it has several properties that uses the generic, but each usage of the interface is a lot lighter).

Side note, the property is named fieldInternalProps because value manager can also be introduced at the picker level in the future, so everything strictly related to the field has been named accordingly.

4. Remove the TEnableAccessibleFieldDOMStructure generic (on v9)

That one is pretty obvious, but once we drop the legacy DOM structure, this generic an be removed:

Here is what the 2. example would look like:

interface SomeInterface<
  TValue extends PickerValidValue,
  TError
> {
  value: TValue;
  referenceDate: PickerValidDate;
  sections: InferFieldSection<TValue>[];
  onError: (error: TError) => void;
  someProperty: SomeProperty;
}

Examples

// Before
type BaseSingleInputFieldProps<
  TValue,
  TDate extends PickerValidDate,
  TSection extends FieldSection,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError,
> = BaseFieldProps<TValue, TDate, TSection, TEnableAccessibleFieldDOMStructure, TError> &
  BaseForwardedSingleInputFieldProps<TEnableAccessibleFieldDOMStructure>;

// After
type BaseSingleInputFieldProps<TValue extends PickerValidValue, TError> = 
  BaseFieldProps<TValue, TError> & BaseForwardedSingleInputFieldProps;
// Before
type BaseMultiInputFieldProps<
  TValue,
  TDate extends PickerValidDate,
  TSection extends FieldSection,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TError,
> = BaseFieldProps<TValue, TDate, TSection, TEnableAccessibleFieldDOMStructure, TError> &
  BaseForwardedMultiInputFieldProps<TEnableAccessibleFieldDOMStructure>;

// After
// Multi input is always range 
// (we could remove `TSection` here even without the other changes described in this document)
type BaseMultiInputFieldProps<TError> =  BaseFieldProps<true, TError> & BaseForwardedMultiInputFieldProps;
// Before
interface DesktopDatePickerSlotProps<
  TDate extends PickerValidDate,
  TEnableAccessibleFieldDOMStructure extends boolean,
> extends BaseDatePickerSlotProps<TDate>,
    ExportedUseDesktopPickerSlotProps<TDate, DateView, TEnableAccessibleFieldDOMStructure> {}

// After
interface DesktopDatePickerSlotProps extends BaseDatePickerSlotProps, ExportedUseDesktopPickerSlotProps<DateView,> {}
// Before
const useField = <
  TValue,
  TDate extends PickerValidDate,
  TSection extends FieldSection,
  TEnableAccessibleFieldDOMStructure extends boolean,
  TForwardedProps extends UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure>,
  TInternalProps extends UseFieldInternalProps<
    any,
    any,
    any,
    TEnableAccessibleFieldDOMStructure,
    any
  >,
>(
  params: UseFieldParams<
    TValue,
    TDate,
    TSection,
    TEnableAccessibleFieldDOMStructure,
    TForwardedProps,
    TInternalProps
  >,
): UseFieldResponse<TEnableAccessibleFieldDOMStructure, TForwardedProps> => { ... }

// After
const useField = <
  TManager extends PickerAnyValueManagerV8,
  TForwardedProps extends UseFieldForwardedProps<
    PickerManagerProperties<TManager>['enableAccessibleFieldDOMStructure']
  >,
>(parameters: UseFieldParameters<TManager, TForwardedProps>): UseFieldResponse<TManager, TForwardedProps> { ... }

Search keywords:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: pickers This is the name of the generic UI component, not the React module! RFC Request For Comments typescript
Projects
None yet
Development

No branches or pull requests

1 participant