How to Type useState Hook with TypeScript in React?

How do I set types on the useState React hook with TypeScript?

I’m migrating a React project to use hooks (React v16.7.0-alpha) with TypeScript, and I’m having trouble setting typings for the destructured elements of useState. Here’s my example:

interface IUser { name: string; }

const [user, setUser] = useState({name: ‘Jon’});

I want to force the user variable to be of type IUser. My only successful approach has been doing it in two phases: typing and then initializing:

let user: IUser; let setUser: any; [user, setUser] = useState({name: ‘Jon’});

But I’m sure there’s a better way to handle this. Additionally, setUser should be initialized as a function that takes an IUser as input and returns nothing.

While using const [user, setUser] = useState({name: ‘Jon’}); without initialization works fine, I would like to take advantage of TypeScript to enforce type checking during initialization, especially when the initial state depends on props.

Can someone help me with the proper way to use useState with TypeScript for typing and initialization?

Ah, good question! A simple way to enforce typing for useState with TypeScript is to explicitly define the type directly when initializing the hook. Here’s how you can do it:

interface IUser {
  name: string;
}

const [user, setUser] = useState<IUser>({ name: 'Jon' });

Why it works: By using <IUser> with useState, TypeScript will enforce that user is always treated as IUser. This also automatically types setUser to expect an IUser as its argument.

If you update the state later, TypeScript will catch any issues if you try to pass in something that doesn’t match the IUser interface. It’s a clean and robust way to handle typing with useState in TypeScript.

Building on Ian’s answer, if your initial state depends on props or other dynamic data, you can use the functional form of useState. This approach is just as type-safe and might be more flexible:

interface IUser {
  name: string;
}

const [user, setUser] = useState<IUser>(() => ({ name: 'Jon' }));

Why this approach is useful: When you use a function to initialize the state, it’s only executed once—at the initial render. This can be especially handy if your initial value is derived from props or some other computation.

Just like before, TypeScript ensures that user adheres to the IUser interface and enforces that setUser accepts only values of the same type. It’s great when you need extra flexibility while still keeping strong type checking in place.

Both previous answers are great for most use cases. However, if you need explicit control over the types for both the state variable and the setter function, you can take an alternative approach by declaring them separately:

interface IUser {
  name: string;
}

let user: IUser;
let setUser: React.Dispatch<React.SetStateAction<IUser>>;
[user, setUser] = useState<IUser>({ name: 'Jon' });

Why this can be helpful: This method gives you full control over the types of both user and setUser. The setter is explicitly typed as React.Dispatch<React.SetStateAction<IUser>>, which ensures it works exactly as expected with TypeScript.

While this approach is more verbose, it can be useful if you’re working in a context where precise typing is essential, or if you’re debugging complex state updates.