TypeScript fails to automatically determine the type of a generic parameter in a React component or hook, requiring explicit type annotation. This typically occurs with hooks like useState, custom generic components, or callback functions where type inference cannot deduce the intended type from context.
This error occurs when TypeScript's type inference system cannot automatically determine what type should be assigned to a generic type parameter in your React code. Generic types are placeholders (like `<T>`) that allow functions and components to work with multiple types while maintaining type safety. TypeScript typically infers generic types from the arguments you pass to a function or component. However, when there isn't enough information in the usage context—such as with `useState(null)`, complex conditional types, or generic components with multiple type parameters—TypeScript cannot make a safe assumption about what type you intend to use. This is particularly common in React when working with hooks like `useState` or `useCallback` initialized with null/undefined values, custom generic components that take data arrays, or render prop patterns where the generic type cannot be inferred from the component's usage alone.
For hooks initialized with null or undefined, provide the type parameter explicitly:
// ❌ Before: Type defaults to undefined
const [user, setUser] = useState(null);
// ✅ After: Explicitly specify the type
interface User {
id: number;
name: string;
}
const [user, setUser] = useState<User | null>(null);This tells TypeScript that the state will eventually hold a User object, even though it starts as null.
When using generic components, provide the type parameter in JSX (TypeScript 2.9+):
// Generic component definition
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: ListProps<T>) {
return <ul>{props.items.map(props.renderItem)}</ul>;
}
// ❌ Before: TypeScript cannot infer T
<List items={[1, 2, 3]} renderItem={(item) => <li>{item}</li>} />
// ✅ After: Explicitly specify the type
<List<number> items={[1, 2, 3]} renderItem={(item) => <li>{item}</li>} />The <number> syntax tells TypeScript what type T should be.
Arrow functions with React.FC don't provide a place for type parameters. Switch to function declarations:
// ❌ Before: No way to add type parameter
const Select: React.FC<SelectProps> = (props) => {
// ...
};
// ✅ After: Function declaration allows generic parameter
function Select<T extends string>(props: SelectProps<T>) {
// ...
}
// Or with React.FC syntax:
function Select<T>(props: SelectProps<T>): React.ReactElement {
// ...
}This gives you a location to define the generic type parameter.
Use the extends keyword to help TypeScript infer types more accurately:
// ❌ Before: T is too broad, inference fails
function useData<T>(key: string): T | null {
// ...
}
// ✅ After: Constrain T to objects with an id
interface Identifiable {
id: number;
}
function useData<T extends Identifiable>(key: string): T | null {
// ...
}
const user = useData<User>('user'); // Now explicit type is requiredType constraints reduce ambiguity and make your intent clear.
Restructure your function so TypeScript can infer all type parameters from the arguments:
// ❌ Before: TypeScript cannot infer T from return type
function createState<T>(): [T | null, (value: T) => void] {
// ...
}
// ✅ After: Accept initial value so T can be inferred
function createState<T>(initialValue: T): [T, (value: T) => void] {
// ...
}
// Or provide a default type factory
function createState<T>(factory: () => T): [T, (value: T) => void] {
// ...
}
// Now inference works
const [count, setCount] = createState(0); // T inferred as numberWhen the generic type appears in function parameters, TypeScript can infer it automatically.
Provide default types for generic parameters that are often the same:
// Define a default type
interface FormProps<T = Record<string, any>> {
initialValues: T;
onSubmit: (values: T) => void;
}
// ✅ Can use without specifying type
<Form initialValues={{ name: '' }} onSubmit={handleSubmit} />
// ✅ Can also override the default when needed
<Form<UserFormData> initialValues={userData} onSubmit={handleSubmit} />Default type parameters reduce the need for explicit annotations in common cases.
Generic Component Patterns: When building reusable generic components, consider using the "constrained generic" pattern where you define a base interface that all generic types must satisfy. This gives TypeScript enough information to provide better autocomplete and error messages while maintaining flexibility.
Inference Limitations: TypeScript's type inference works by analyzing the flow of data through your code, but it has limitations with deeply nested conditional types, recursive types, or when types depend on runtime values. In these cases, explicit type annotations are not just helpful—they're necessary for type safety.
React.FC vs Function Declarations: The React.FC (or React.FunctionComponent) type is convenient but doesn't play well with generics. For generic components, prefer function declarations or explicit function signatures without React.FC. This also gives you better control over the component's return type.
Generic Constraints and extends: Using extends with generics serves two purposes: it helps TypeScript narrow down inference possibilities and provides compile-time guarantees about what operations are valid on the generic type. For example, <T extends { id: number }> ensures T will always have an id property.
useState Type Inference: The useState hook uses overloads to provide good inference when initialized with a value, but the useState(undefined) or useState(null) cases default to undefined. Always explicitly type useState when starting with falsy values that will later hold real data.
Performance Considerations: Type annotations don't affect runtime performance, only compile time. However, overly complex generic constraints can slow down TypeScript's type checker in large projects. Strike a balance between type safety and compilation speed by keeping generic constraints simple and focused.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite