Typing Component Props, Events, and Hooks

TypeScript really shines in React when working with component props, events, and hooks like useState or useEffect. Here’s how to type them properly so you get type safety and helpful autocompletions.

Typing Component Props

interface Todo {
  title: string;
  done: boolean;
}

interface TodoItemProps {
  todo: Todo;
  onToggle: (title: string) => void;
}

const TodoItem: React.FC<TodoItemProps> = ({ todo, onToggle }) => (
  <div>
    <input
      type="checkbox"
      checked={todo.done}
      onChange={() => onToggle(todo.title)}
    />
    {todo.title}
  </div>
);

💡 Tip: Try it in your IDE!

With typed props, you’ll get autocompletion and instant feedback — making mistakes harder and coding faster.

Typing useState

Step 1: Create the state type

interface Todo {
  title: string;
  done: boolean;
}

Step 2: Type the useState hook

// empty initial state
const [todos, setTodos] = useState<Todo[]>([]);
const INITIAL_TODO: Todo[] = [
  {
    title: "first task",
    done: false,
  },
  {
    title: "second task",
    done: true,
  },
  {
    title: "third task",
    done: true,
  },
];

const [todos, setTodos] = useState<Todo[]>(INITIAL_TODO);

Now todos is typed as an array of Todo objects, and setTodos will only accept that type.

Step 3: Using setTodos

Here are three valid and clean ways to use setTodos:

// 1. Basic usage — straightforward and readable
setTodos([{ title: "new todo", done: false }]);

// 2. Callback form — preferred if referencing previous state or writing multiple lines
setTodos((prevTodos) => {
  return [...prevTodos, { title: "new todo", done: false }];
});

// 3. One-liner callback — clean if short
setTodos((prev) => [...prev, { title: "new todo", done: false }]);

🧠 Why use the callback form?

React batches updates — using the callback ensures you’re working with the most recent state, especially inside useEffect or async operations.

Typing Events

React’s synthetic events are strongly typed too. Here’s an example with an input change handler:

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

Typing Custom Hooks

If you create your own hooks, type the input and output clearly:

function useTodoList(): [Todo[], (todo: Todo) => void] {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodo = (todo: Todo) => setTodos((prev) => [...prev, todo]);

  return [todos, addTodo];
}
const [todos, addTodo] = useTodoList();
addTodo({ title: "Learn TS", done: false });

Last updated