TypeScript with React, Redux, and Hooks
In this article, we will set up a ReactJS project with TypeScript support and discuss how to handle type checking for components, hooks, and events.
1. Creating a React Project with TypeScript
We can easily create a TypeScript-based React project using the following command:
npx create-react-app learn-typescript --template typescript
This command creates the project structure with a tsconfig.json file and uses .tsx extensions for React components instead of .js.
2. Defining Functional Components
There are two common ways to define functional components in TypeScript.
Method 1: Using Type Inference for Props
interface ChildProps {
color: string;
children?: React.ReactNode;
}
const Child = ({ color }: ChildProps) => {
return <p style=>Child Component</p>;
};
const Parent = () => {
return <Child color="red" />;
};
In this case, Child is just a standard JavaScript function that takes arguments matching the ChildProps interface.
Method 2: Using React.FC
interface ChildProps {
color: string;
}
const Child: React.FC<ChildProps> = ({ color, children }) => {
return (
<div>
<p style=>Child Component</p>
{children}
</div>
);
};
With React.FC (Functional Component), TypeScript automatically includes the children prop (in older versions of React types) and provides types for properties like defaultProps and displayName.
Note: As of React 18,
childrenis no longer included implicitly inReact.FC. best practice suggests defining props explicitly (Method 1) or addingchildrento your interface manually.
3. Typing useState Hook
// TypeScript infers 'string'
const [state, setState] = useState("");
In the code above, TypeScript automatically infers the type of state as string. However, for arrays or complex objects, we need to be explicit.
// Explicitly setting type as string array
const [items, setItems] = useState<string[]>([]);
Union Types in State
Sometimes a state can be null, undefined, or an object. We can use union types for this:
interface User {
name: string;
age: number;
}
const [user, setUser] = useState<User | null>(null);
4. Typing Events
Handling events like onChange or onSubmit requires specific React event types.
Inline Handlers (Inferred)
return (
<form>
{/* TypeScript infers the event type here automatically */}
<input onChange={(e) => console.log(e.target.value)} name="demoField" />
</form>
);
Defined Handlers (Explicit)
When defining the handler outside JSX, you must specify the event type.
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<form>
<input onChange={handleChange} name="demoField" />
</form>
);
Common Event Types:
React.ChangeEvent<HTMLInputElement>React.FormEvent<HTMLFormElement>React.MouseEvent<HTMLButtonElement>
5. TypeScript with Redux (Reducer Example)
When using Redux with TypeScript, we need to define types for our State and Actions.
// 1. Define the State Interface
interface CounterState {
count: number;
}
// 2. Define Action Types
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' };
const initialState: CounterState = { count: 0 };
// 3. Create the Reducer with types
const counterReducer = (state: CounterState = initialState, action: Action): CounterState => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
};
Using Redux Toolkit (recommended) simplifies this greatly as it auto-generates types, but understanding the underlying type structure is useful.