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>>];
// ...
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>
)
}
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>
)
}
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>
)
}
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"
}
]
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;
}
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>
)
}
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>
)
}
Now you can use the setState
in a more professional way.
Give me feedback.
Thanks!!
Top comments (6)
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.
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
Great article ;)
Muito bom, estava apanhando pra conseguir fazer isso, me ajudou muito, vlw!
Great article!
Thank you!! :D