DEV Community

Cover image for React Hook useState in TypeScript
Gabriel Rufino
Gabriel Rufino

Posted on

React Hook useState in TypeScript

Typescript brought great evolution to the JavaScript and ReactJS ecosystem. More productivity, software more robust and reliable, interfaces, and error prediction during development are some advantages of use TypeScript in your project.

This is a post in English written by a Brazilian. I decided to try to write a series of articles in a new language with more reach than Portuguese using a translator eventually because I'm not fluent yet. So, I ask you for feedback and corrections if you find any errors. Thanks!

Here, I'll show you how to declare the type of a state when you use the React Hook useState.

First of all, look at the useState method description at the types file of the React API:

// ...
/**
 * Returns a stateful value, and a function to update it.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usestate
 */
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
// convenience overload when first argument is ommitted
/**
 * Returns a stateful value, and a function to update it.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usestate
 */
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
// ...
Enter fullscreen mode Exit fullscreen mode

Note that there are two definitions of the hook. The second definition overloads the first, giving the possibility of don't explicit the type of the state.

The main thing that you have note is that the method receives a TypeScript Generic called S. Through it, you can define the type of the state.

Look at these basic examples:

import React, {useState} from 'react'

export default function App() {
  const [name, setName] = useState<string>('Gabriel Rufino')
  const [age, setAge] = useState<number>(21)
  const [isProgrammer, setIsProgrammer] = useState<boolean>(true)

  return (
    <div>
      <ul>
        <li>Name: {name}</li>
        <li>Age: {age}</li>
        <li>Programmer: {isProgrammer ? 'Yes' : 'No'}</li>
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

If you try to set a state with a value that does not match the type, you'll cause an error:

import React, {useEffect, useState} from 'react'

export default function App() {
  const [name, setName] = useState<string>('Gabriel Rufino')
  const [age, setAge] = useState<number>(21)
  const [isProgrammer, setIsProgrammer] = useState<boolean>(true)

  useEffect(() => {
    // Error: Argument of type '21' is not assignable to parameter of type 'SetStateAction<string>'.ts(2345)
    setName(21)
    // Error: Argument of type 'true' is not assignable to parameter of type 'SetStateAction<number>'.ts(2345)
    setAge(true)
    // Error: Argument of type '"Gabriel Rufino"' is not assignable to parameter of type 'SetStateAction<boolean>'.
    setIsProgrammer('Gabriel Rufino')
  }, [])

  return (
    <div>
      <ul>
        <li>Name: {name}</li>
        <li>Age: {age}</li>
        <li>Programmer: {isProgrammer ? 'Yes' : 'No'}</li>
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

But for primary types, you don't need to make the type explicit, since typescript can infer them. Look:

import React, {useEffect, useState} from 'react'

export default function App() {
  const [name, setName] = useState('Gabriel Rufino')
  const [age, setAge] = useState(21)
  const [isProgrammer, setIsProgrammer] = useState(true)

  useEffect(() => {
    // Error: Argument of type '21' is not assignable to parameter of type 'SetStateAction<string>'.ts(2345)
    setName(21)
    // Error: Argument of type 'true' is not assignable to parameter of type 'SetStateAction<number>'.ts(2345)
    setAge(true)
    // Error: Argument of type '"Gabriel Rufino"' is not assignable to parameter of type 'SetStateAction<boolean>'.
    setIsProgrammer('Gabriel Rufino')
  }, [])

  return (
    <div>
      <ul>
        <li>Name: {name}</li>
        <li>Age: {age}</li>
        <li>Programmer: {isProgrammer ? 'Yes' : 'No'}</li>
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

The advantage comes when you store data more complex like objects or arrays. Suppose that we want to store an array of users like this:

[
  {
    "id": 1,
    "name": "Gabriel Rufino",
    "email": "contato@gabrielrufino.com"
  },
  {
    "id": 1,
    "name": "Darth Vader",
    "email": "darthvader@starwars.com"
  },
  {
    "id": 1,
    "name": "Luke Skywalker",
    "email": "lukeskywalker@starwars.com"
  }
]
Enter fullscreen mode Exit fullscreen mode

We can define an interface that represents the format of a user. In this case, we should write some like:

interface IUser {
  id: number;
  name: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

Now, we can write our component and put this data in a state with that type IUser[], that represents an array of objects with the format IUser:

import React, {useState} from 'react'

interface IUser {
  id: number;
  name: string;
  email: string;
}

export default function Users() {
  const [users, setUsers] = useState<IUser[]>([
    {
      id: 1,
      name: 'Gabriel Rufino',
      email: 'contato@gabrielrufino.com'
    },
    {
      id: 1,
      name: 'Darth Vader',
      email: 'darthvader@starwars.com'
    },
    {
      id: 1,
      name: 'Luke Skywalker',
      email: 'lukeskywalker@starwars.com'
    }
  ])

  return (
    <div>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name} - {user.email}</li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

But, this is usually not the way it works. Normally, we get data from an API asynchronously.

import React, {useState, useEffect} from 'react'
import axios from 'axios'

interface IUser {
  id: number;
  name: string;
  email: string;
}

export default function Users() {
  const [users, setUsers] = useState<IUser[]>([])

  useEffect(() => {
    axios.get<IUser[]>('https://api.yourservice.com/users')
      .then(({ data }) => {
        setUsers(data)
      })
  }, [])

  return (
    <div>
      <ul>
        {users.map((user: IUser) => (
          <li key={user.id}>{user.name} - {user.email}</li>
        ))}
      </ul>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now you can use the setState in a more professional way.

Give me feedback.
Thanks!!

Top comments (6)

Collapse
 
juliusgoddard profile image
Julius Goddard

This is all good apart from one issue:

The code in the last example creates an Object of users, instead of an Array of Objects - therefore this code will return users.map is not a function because .map can only be used on an Array of Objects, instead of an Object.

Collapse
 
78eder78 profile image
Junior

Hey Gabriel ótimo artigo, parabéns garoto.
Obrigado pour compartilhar, vou seguir vc no twitter ;)

Amazing article Gabriel, thanks for sharing :)
I'm following you on twitter.
Keep it up

Collapse
 
dalalrohit profile image
Rohit Dalal

Great article ;)

Collapse
 
renato_zero6 profile image
Renato Rebouças

Muito bom, estava apanhando pra conseguir fazer isso, me ajudou muito, vlw!

Collapse
 
luispeerez profile image
Luis Perez Bautista

Great article!

Collapse
 
gabrielrufino profile image
Gabriel Rufino

Thank you!! :D