useState Typing
useState Typing
Basic useState
import { useState } from 'react';
// Type inferred automatically
const [count, setCount] = useState(0); // number
const [name, setName] = useState('Alice'); // string
const [active, setActive] = useState(false); // boolean
// Explicit type for initial value
type Status = 'idle' | 'loading' | 'success' | 'error';
const [status, setStatus] = useState<Status>('idle');
// Union type with explicit generic
const [user, setUser] = useState<User | null>(null);
Complex State
interface Todo {
id: number;
text: string;
completed: boolean;
}
// Array state
const [todos, setTodos] = useState<Todo[]>([]);
// Object state
interface FormState {
name: string;
email: string;
errors: Record<string, string>;
}
const [form, setForm] = useState<FormState>({
name: '',
email: '',
errors: {}
});
// Functional update
setForm(prev => ({ ...prev, name: 'Bob' }));
Reducer Pattern
import { useReducer } from 'react';
interface State {
count: number;
step: number;
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'setStep'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + state.step };
case 'decrement':
return { ...state, count: state.count - state.step };
case 'setStep':
return { ...state, step: action.payload };
}
}
const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });
useEffect and Lifecycle
useEffect and Lifecycle Hooks
Basic useEffect
import { useEffect, useState } from 'react';
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
// Effect with cleanup
const controller = new AbortController();
fetchUser(userId, controller.signal)
.then(setUser)
.catch(console.error);
return () => controller.abort();
}, [userId]); // Dependency array typed automatically
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
Custom Hook with useEffect
function useFetch<T>(url: string): {
data: T | null;
loading: boolean;
error: string | null;
} {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then((json: T) => {
setData(json);
setLoading(false);
})
.catch(err => {
if (err.name !== 'AbortError') {
setError(err.message);
setLoading(false);
}
});
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Usage
const { data: users, loading, error } = useFetch<User[]>('/api/users');
useCallback and useMemo
useCallback and useMemo
useCallback Typing
import { useCallback, useState } from 'react';
interface TodoItem {
id: number;
text: string;
completed: boolean;
}
function TodoList({ todos }: { todos: TodoItem[] }) {
const [filter, setFilter] = useState<string>('all');
// Return type inferred as (todo: TodoItem) => boolean
const filteredTodos = useCallback(
(todo: TodoItem): boolean => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
return true;
},
[filter]
);
const handleToggle = useCallback(
(id: number) => {
// Use functional update for complex state
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
},
[]
);
return (
<ul>
{todos.filter(filteredTodos).map(todo => (
<li key={todo.id} onClick={() => handleToggle(todo.id)}>
{todo.text}
</li>
))}
</ul>
);
}
useMemo Typing
function expensiveCalculation(data: number[]): number {
return data.reduce((acc, val) => acc + val * Math.random(), 0);
}
function Dashboard({ numbers }: { numbers: number[] }) {
// Return type inferred as number
const result = useMemo(() => expensiveCalculation(numbers), [numbers]);
return <div>Result: {result}</div>;
}
useRef and useImperativeHandle
useRef and useImperativeHandle
useRef Typing
import { useRef } from 'react';
function VideoPlayer() {
// DOM element ref
const videoRef = useRef<HTMLVideoElement>(null);
// Mutable ref for values
const intervalRef = useRef<number | null>(null);
const play = () => {
videoRef.current?.play();
};
const pause = () => {
videoRef.current?.pause();
};
return (
<>
<video ref={videoRef} src="video.mp4" />
<button onClick={play}>Play</button>
<button onClick={pause}>Pause</button>
</>
);
}
useImperativeHandle
import { useImperativeHandle, useRef } from 'react';
interface InputHandle {
focus: () => void;
clear: () => void;
getValue: () => string;
}
const FancyInput = forwardRef<InputHandle, {}>((props, ref) => {
const inputRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => { if (inputRef.current) inputRef.current.value = ''; },
getValue: () => inputRef.current?.value ?? ''
}));
return <input ref={inputRef} type="text" />;
});
Context Typing
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}