Default generic types let you write a single component that works with a specific type by default, but can be overridden when needed. This guide walks through adding defaults to your TypeScript component props, so callers get full type safety without needing to pass type arguments every time. You’ll end up with cleaner, more flexible components that are easier to use and maintain.
What You’ll Need
- A TypeScript project (version 3.0 or later, though modern versions are recommended)
- A React component (or any function component) that you want to make generic
- Basic familiarity with TypeScript generics and React props
Step-by-Step
- Identify the prop that should be generic. Look for a prop where the type is too narrow or too loose. Common examples: a value prop that could be a string, number, or object; an onChange callback that should match the value type; or a data prop that holds a list of items.
- Add a type parameter to the component’s props interface. Define a generic interface, e.g.,
interface MyComponentProps<T> { value: T; onChange: (value: T) => void; }. This makes the component’s props depend on a type variable. - Give the type parameter a default. In the interface, write
interface MyComponentProps<T = string> { ... }. The= stringis the default type. Now, if a caller doesn’t specify the type, TypeScript will assumeTisstring. - Make the component function generic. In the function declaration, add the type parameter with the same default:
function MyComponent<T = string>(props: MyComponentProps<T>) { ... }. This tells TypeScript that the component itself is generic and can infer the type from usage. - Use the generic type in the component body. Replace any
anyor hard-coded types withTwhere appropriate. For example, inside the component, a state variable might be typed asuseState<T>(props.value). - Test with and without explicit type arguments. Use the component without angle brackets:
<MyComponent value="hello" onChange={(v) => console.log(v)} />. TypeScript should inferTasstring. Then try with an explicit type:<MyComponent<number> value={42} onChange={(v) => console.log(v)} />. Both should compile without errors. - Consider using the default in complex generic constraints. If your generic type has a constraint, like
T extends object, you can still provide a default:<T extends object = { id: string }>. This ensures the default type satisfies the constraint. - Export the props interface. Export the generic interface so other files can use it:
export interface MyComponentProps<T = string> { ... }. This helps with type reuse and documentation.
Common Mistakes
- Forgetting the default on the component function. If you add a default to the interface but not to the function, TypeScript may still require type arguments in some cases. Always add the default to both.
- Using a default that doesn’t match the constraint. If you have
T extends numberand set a default ofstring, TypeScript will error. Make sure the default satisfies the constraint. - Overusing generics. Not every prop needs to be generic. If a prop is always a string, don’t make it generic. Overuse adds complexity without benefit.
- Ignoring inference. Sometimes TypeScript can infer the type from the props, so you might not need a default at all. But defaults are useful when you want a sensible fallback when inference isn’t possible.
Tips
- Use descriptive default types. If the default is a common type like
stringornumber, it’s clear. If it’s an object, consider defining an interface and using that as the default. - Combine with
extendsfor flexibility. UseT extends SomeBase = SomeBaseto allow subtypes while keeping the base as default. - Document the generic. Add a JSDoc comment explaining what
Trepresents and when to override the default. - Use type inference to your advantage. In many cases, you don’t need to specify the type argument at all because TypeScript infers it from the props. Defaults only kick in when inference fails or when the component is used without props that define the type.
Troubleshooting
If your component doesn’t behave as expected, check these common issues:
- TypeScript errors about missing type arguments. This usually means you forgot the default on the function or the interface. Double-check both declarations.
- Inference isn’t working. If the component is used in a way that doesn’t provide enough information, TypeScript falls back to the default. If that’s not what you want, provide an explicit type argument at the call site.
- Errors inside the component body. If you use
Tin a way that requires specific properties, make sure the default type has those properties. Otherwise, TypeScript will complain. You might need to add a constraint. - Unexpected behavior with
useStateor callbacks. If you have a generic component with state, ensure the state type matchesT. For example,useState<T>(props.value)works, butuseState(props.value)might infer a union type.
Frequently Asked Questions
- Can I have multiple generic types with defaults? Yes, you can have multiple type parameters, each with its own default:
<T = string, U = number>. Just be consistent between the interface and the function. - What happens if I don’t provide a default and the caller doesn’t specify the type? TypeScript will try to infer the type from usage. If it can’t, it will fall back to
unknown(or the constraint if you have one). This can lead to less type safety, so defaults are helpful. - Can I use a default type that is a union? Yes, you can use any type as a default, including unions:
<T = string | number>. This is useful when the prop can be multiple types, but you want a sensible default. - Does this work with class components? Yes, you can do the same with class components by adding the type parameter to the class and the props interface. The syntax is slightly different but the concept is the same.
- Will this work with React.memo or forwardRef? Yes, but you need to be careful with the type parameters. For
forwardRef, you’ll need to wrap the generic component in a function that accepts the type parameter. It’s a bit more verbose, but it works. - Is there a performance impact? No, generic types are erased at compile time. There is no runtime cost.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Philips DST7060/20 7000 Series Iron: Specs & Fit
- Coal Fueled Jet Aircraft: Could It Work?675 L
- Remote Desktop on Raspberry Pi: How to Enable It
- Word Select Last Line in Paragraph?
- Google Assistant IFTTT Recipes: 7 Useful Automations
- OLED TVs in Dark Rooms: Why They Look Better and When They Don’t