Context: During the recent refactoring of the forms designer in the debugger, several issues were identified causing side effects, unnecessary updates, and re-renders. The root cause is the current implementation of the InputComponent.
We must leverage TypeScript to its full potential. Using it merely as a fancy linter negates its primary benefits.
The use of any is now restricted. If the type of a constant, argument, or property is unknown, use the unknown type instead. This forces explicit type checking and is much safer.
For type checking, use type guards instead of direct type casting. This is the correct, type-safe approach and is possible in most scenarios.
Creating a single, flattened type that contains all possible properties is a bad practice. It leads to confusing and error-prone code. Instead, use subtypes with a discriminator property (e.g., type or itemType).
Example: Using Discriminated Unions and Type Guards
// Define specific subtypes
type TextInputProps = {
type: 'text';
value: string;
maxLength: number;
};
type NumberInputProps = {
type: 'number';
value: number;
min: number;
max: number;
};
// Create a union type
type InputProps = TextInputProps | NumberInputProps;
// Use a type guard to check the type
function isTextInput(props: InputProps): props is TextInputProps {
return props.type === 'text';
}
// Component logic can now safely handle each type
function MyComponent(props: InputProps) {
if (isTextInput(props)) {
// TypeScript knows `props` is TextInputProps here
console.log(props.maxLength);
} else {
// TypeScript knows `props` is NumberInputProps here
console.log(props.min);
}
}
Default values must be part of the model initialization logic, not the editor component.
A controlled editor should follow this base structure:
type EditorProps<TValue> = {
value: TValue;
onChange: (newValue: TValue) => void;
}
This can be extended with additional props like readOnly, but the core contract of value and onChange must remain consistent.
The current InputComponent is implemented as a monolithic component that renders all possible editors. Its props are a flattened set of properties from every supported editor.
This has led to:
-
Different internal editors using different combinations of
valueanddefaultValue -
Some editors incorrectly using the
useEffecthook to reset values to a default -
Confirmed source of existing bugs and high risk for future ones
The defaultValue pattern is being removed entirely from controlled editors.
When reviewing code:
-
Flag any use of
anytype - should useunknowninstead -
Flag type casting without type guards - prefer type guards
-
Flag monolithic types - should use discriminated unions
-
Flag
defaultValuein controlled editor components - defaults should be in model initialization -
Flag incorrect
value/onChangepatterns - controlled components must follow the standard contract -
Flag
useEffectused for value resets - this causes unnecessary re-renders
Do not accept or suggest reintroducing the defaultValue pattern in controlled editor components.