docs/react-v9/contributing/rfcs/react-components/convergence/remove-commons.md
___Commons typesContributors: @ecraig12345
To make our components' public API contracts clearer to users and improve internal consistency:
FooCommons typesFooPropsPick to define FooState/FooContextCurrently, we have a pattern of a FooCommons interface in a lot of components for things that are shared between props and state (and sometimes context). For example:
export type InputCommons = {
// These are optional in props (made required in state)
size?: 'small' | 'medium' | 'large';
appearance?: 'outline' | 'underline' | 'filledDarker' | 'filledLighter';
};
export type InputProps = InputCommons &
ComponentProps<InputSlots> & {
// maybe some other props here
};
export type InputState =
// useInput provides defaults for things in InputCommons when creating the state object
Required<InputCommons> &
ComponentState<InputSlots> & {
// maybe some other state here
};
// Sometimes there's also a Context type which uses Commons
Other components use a similar pattern, except with things marked as required in FooCommons and using Partial<FooCommons> to define FooProps. For example:
export type CheckboxCommons = {
// These are required in state (made optional in props)
size: 'medium' | 'large';
};
export type CheckboxProps =
// Things in CheckboxCommons are optional in props
Partial<CheckboxCommons> &
ComponentProps<CheckboxSlots> & {
// maybe some other props here
};
export type CheckboxState =
// Commons are required in state
CheckboxCommons &
ComponentState<CheckboxSlots> & {
// maybe some other state here
};
Our current use of FooCommons interfaces as part of component props could cause unnecessary confusion for a user looking at our props types (which are the only types users will care about 99% of the time):
We're also inconsistent about how required/optional things are handled in commons and props--whether things are:
type FooState = Required<FooCommons> & ....type FooProps = Partial<FooCommons> & ...Rather than making a consistent rule for whether commons should more closely reflect props or state, I think a better solution is to remove the FooCommons types entirely.
Instead, put everything in FooProps, and mark props as required or optional based on what's actually required for the user to pass in to the component.
Since FooState/FooContext are implementation details that only the component author and people with advanced recomposition use cases need to think about, define them with Pick/Required/etc from FooProps.
This makes what's included and required/optional in the user-facing type (props) very explicit and confines the implementation details to the "internal"/advanced use types (state, context).
Modified Input example from above:
export type InputProps = ComponentProps<InputSlots> & {
size?: 'small' | 'medium' | 'large';
appearance?: 'outline' | 'underline' | 'filledDarker' | 'filledLighter';
// maybe some other props here
};
export type InputState =
// If there are things from props which remain optional in state, add another Pick without Required
Required<Pick<InputProps, 'size' | 'appearance'>> &
ComponentState<InputSlots> & {
// maybe some other state here
};
Modified Checkbox example (now using an identical pattern):
export type CheckboxProps = ComponentProps<CheckboxSlots> & {
size?: 'medium' | 'large';
// maybe some other props here
};
export type CheckboxState = Required<Pick<CheckboxProps, 'size'>> &
ComponentState<CheckboxSlots> & {
// maybe some other state here
};
As an interim step (to avoid blocking RC), we'll stop exporting FooCommons types, implemented in this PR. This way, if the actual changes aren't finished before RC, we could finish changing the types later without breaking public APIs (since the final included props and their optional/required status should be the same either way).
FooState type definition, especially when FooState includes most/all of FooCommons
Required<FooProps> won't work since FooProps also includes all the native propsn/a