Type useState as Array or Object in React TypeScript

avatar
Borislav Hadzhiev

Last updated: Feb 29, 2024
9 min

banner

# Table of Contents

  1. Type useState as Array of STRINGS in React TypeScript
  2. Type useState as Array of OBJECTS in React TypeScript
  3. Type useState as an OBJECT in React TypeScript
  4. Type useState with EMPTY OBJECT initial value in React

# Type useState as Array of Strings in React TypeScript

To type the useState hook as an array of strings in React, use the hook's generic.

The state variable can be initialized to an empty array or an array of strings and will only accept string values.

App.tsx
import React from 'react'; import {useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const names: string[] const [names, setNames] = useState<string[]>([]); return ( <div> <button onClick={() => setNames(prevNames => [...prevNames, 'Bob'])}> Add name </button> {names.map((element, index) => { return ( <div key={index}> <h2>{element}</h2> </div> ); })} </div> ); }; export default App;

type use state as array of strings in react typescript

The code for this article is available on GitHub

The code sample uses a type of string[], but you could also use number[] if you need to store an array of numbers or boolean[] to store an array of booleans in your React state.

We used a generic to type the useState hook correctly while initializing the hook with an empty array.

react usestate string array typescript

Had we not used the generic, e.g. useState<string[]>([]) when typing the hook, the type of the state variable would be never[], in other words, an array that will never contain any elements.

If you pass even a single empty string to the array, TypeScript will be able to infer the type of the state variable.

App.tsx
import React from 'react'; import {useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const names: string[] const [names, setNames] = useState(['']); return ( <div> <button onClick={() => setNames(prevNames => [...prevNames, 'Bob'])}> Add name </button> {names.map((element, index) => { return ( <div key={index}> <h2>{element}</h2> </div> ); })} </div> ); }; export default App;

Notice that we didn't even have to use a generic to type the state variable. TypeScript was able to infer the type based on the provided initial value.

However, it is a best practice to always explicitly type the useState hook, especially when working with arrays and objects.

# Trying to add a value of a different type causes an error

If we try to add a value of a different type to the state array, we would get a type checking error.

App.tsx
import React from 'react'; import {useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const names: string[] const [names, setNames] = useState<string[]>([]); // โ›”๏ธ Argument of type '(prevNames: string[]) => (string | number)[]' is not // assignable to parameter of type 'SetStateAction<string[]>'. setNames(prevNames => [...prevNames, 1000]); return ( <div> <button onClick={() => setNames(prevNames => [...prevNames, 'Bob'])}> Add name </button> {names.map((element, index) => { return ( <div key={index}> <h2>{element.toUpperCase()}</h2> </div> ); })} </div> ); }; export default App;
The code for this article is available on GitHub

The example shows how trying to add a number to a state array that is typed as string[] causes the type checker to error out.

# Table of Contents

  1. Type useState as Array of OBJECTS in React TypeScript
  2. Type useState as an OBJECT in React TypeScript
  3. Type useState with EMPTY OBJECT initial value in React
  4. Type the useState hook with NULL initial value in React

# Type useState as Array of Objects in React TypeScript

To type the useState hook as an array of objects in React, use the hook's generic.

The state variable can be initialized to an empty array and will only accept objects of the specified type.

App.tsx
import React from 'react'; import {useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const employees: {salary: number;name: string;}[] const [employees, setEmployees] = useState<{salary: number; name: string}[]>( [], ); return ( <div> <button onClick={() => setEmployees(prevEmployees => [ ...prevEmployees, {salary: 100, name: 'Bob'}, ]) } > Add employee </button> {employees.map((employee, index) => { return ( <div key={index}> <h2> salary: {employee.salary} / name: {employee.name} </h2> </div> ); })} </div> ); }; export default App;
The code for this article is available on GitHub

We used a generic to type the useState hook correctly while initializing the hook with an empty array.

react typescript usestate array of objects

Had we not used the generic, e.g. useState<{salary: number; name: string}[]>([]) when typing the hook, the type of the state variable would be never[], in other words, an array that will never contain any elements.

# Type useState as Array of Objects using a Type Alias or an Interface

You can also use a type alias or an interface if the call to the useState hook gets busy.

App.tsx
import React from 'react'; import {useState} from 'react'; type Employee = { salary: number; name: string; }; const App = () => { // ๐Ÿ‘‡๏ธ const employees: Employee[] const [employees, setEmployees] = useState<Employee[]>([]); return ( <div> <button onClick={() => setEmployees(prevEmployees => [ ...prevEmployees, {salary: 100, name: 'Bob'}, ]) } > Add employee </button> {employees.map((employee, index) => { return ( <div key={index}> <h2> salary: {employee.salary} / name: {employee.name} </h2> </div> ); })} </div> ); }; export default App;

We extracted the object type into a type alias and used it as Employee[] to type the useState hook.

# Adding a value of a different type causes an error

If we try to add a value of a different type to the state array, we would get a type checking error.

App.tsx
import React from 'react'; import {useState} from 'react'; type Employee = { salary: number; name: string; }; const App = () => { // ๐Ÿ‘‡๏ธ const employees: Employee[] const [employees, setEmployees] = useState<Employee[]>([]); // โ›”๏ธ Argument of type '(prevEmployees: Employee[]) => (string | Employee)[]' is not assignable to parameter of type 'SetStateAction<Employee[]>'. setEmployees(prevEmployees => [...prevEmployees, 'Hello world']); return ( <div> <button onClick={() => setEmployees(prevEmployees => [ ...prevEmployees, {salary: 100, name: 'Bob'}, ]) } > Add employee </button> {employees.map((employee, index) => { return ( <div key={index}> <h2> salary: {employee.salary} / name: {employee.name} </h2> </div> ); })} </div> ); }; export default App;
The code for this article is available on GitHub

The example shows how trying to add a string to a state array that is typed as Employee[] causes the type checker to error out.

# Table of Contents

  1. Type useState as an OBJECT in React TypeScript
  2. Type useState with EMPTY OBJECT initial value in React
  3. Type the useState hook with NULL initial value in React

# Type useState as an Object in React TypeScript

Use the hook's generic to type the useState hook as an object in React. The state variable will only accept key-value pairs of the specified type.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {name: string; salary: number;} const [employee, setEmployee] = useState<{name: string; salary: number}>({ name: '', salary: 0, }); useEffect(() => { setEmployee({name: 'James', salary: 100}); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;

type use state as object in react typescript

We used a generic to type the useState hook correctly while initializing the hook with an object.

# Marking properties on the object as optional

Sometimes you might not want to set initial values for all of the object's properties. In this case, you can mark the properties as optional.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ mark salary as optional const [employee, setEmployee] = useState<{ name: string; salary?: number }>({ name: '', }); useEffect(() => { setEmployee({name: 'James', salary: 100}); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;
The code for this article is available on GitHub

We used a question mark to mark the salary property as optional.

The property can either store an undefined value or a value of type number.

This is why we aren't required to provide it when initializing the state object.

# When all initial values are provided, TypeScript infers the type

If you provide initial values for all of your object's properties, TypeScript will be able to infer the type of the state variable.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {name: string;salary: number;} // โœ… typed correctly without a generic const [employee, setEmployee] = useState({ name: '', salary: 0, }); useEffect(() => { setEmployee({name: 'James', salary: 100}); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;

We passed initial values for all of the object's properties, which enabled TypeScript to type the employee variable correctly.

However, it is a best practice to always explicitly type the useState hook, especially when working with arrays and objects.

# When you don't know all of the properties in advance

In some cases, you might not know all of the properties you will set on the object in advance.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ flexible object type const [employee, setEmployee] = useState<{[key: string]: any}>({}); useEffect(() => { setEmployee({ name: 'James', salary: 100, department: 'Dev', tasks: ['dev', 'test', 'ship'], }); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;
The code for this article is available on GitHub

The {[key: string]: any} syntax is an index signature in TypeScript and is used when we don't know all the names of a type's properties and the shape of the values ahead of time.

The index signature in the example means that when an object is indexed with a string, it will return a value of any type.

You can use this approach when you don't know all of the object's properties in advance.

# Setting object property to be one of multiple types

If you want to set an object property to be one of multiple types, use a union.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { const [employee, setEmployee] = useState<{ name: string; // ๐Ÿ‘‡๏ธ string OR number salary: string | number; }>({ name: '', salary: '', }); useEffect(() => { setEmployee({name: 'James', salary: 100}); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;

We used a union to set the salary property to be of type string or number.

I've also written a tutorial on defining an array with multiple types.

# Extract the type of useState into a Type Alias or an Interface

If your useState hook gets busy, extract the type you pass to the generic into a type alias or an interface.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; type Employee = { name: string; salary: number; }; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {name: string; salary: number;} const [employee, setEmployee] = useState<Employee>({ name: '', salary: 0, }); useEffect(() => { setEmployee({name: 'James', salary: 100}); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;
The code for this article is available on GitHub

The syntax useState<Employee> is much easier to read, especially when dealing with large objects.

# Table of Contents

  1. Type useState with EMPTY OBJECT initial value in React
  2. Type the useState hook with NULL initial value in React

# Type useState with empty Object initial value in React (TS)

You can also use the useState hook's generic to type it with an initial value of an empty object.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {[key: string]: any;} const [employee, setEmployee] = useState<{[key: string]: any}>({}); useEffect(() => { setEmployee({ name: 'Alice', salary: 100, department: 'Dev', tasks: ['dev', 'test', 'ship'], }); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;

The {[key: string]: any} syntax is an index signature in TypeScript and is used when we don't know all the names of a type's properties and the shape of the values ahead of time.

The index signature in the example means that when the object is indexed with a string, it will return a value of any type.

You can use this approach when you don't know all of the object's properties in advance.

# Overriding the type of specific object properties

You might try to override the type of a specific property with an index signature.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; type Employee = { [key: string]: any; age?: number; tasks?: string[]; }; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {[key: string]: any;} const [employee, setEmployee] = useState<Employee>({}); useEffect(() => { setEmployee({ name: 'Alice', salary: 100, department: 'Dev', tasks: ['dev', 'test', 'ship'], }); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;
The code for this article is available on GitHub

We marked the age and tasks properties as optional and specified exact types for them.

Optional properties can either have an undefined value or be of the specified type.

This is why we are still able to initialize the state object as empty.

However, providing types for the properties that we know about in advance is useful, because the age and tasks properties can only be set to the specified type.

# Using a union type

If some of the properties of the object can be of multiple types, use a union.

App.tsx
import React from 'react'; import {useEffect, useState} from 'react'; type Employee = { [key: string]: any; // ๐Ÿ‘‡๏ธ age is number OR string age?: number | string; tasks?: string[] | number[]; }; const App = () => { // ๐Ÿ‘‡๏ธ const employee: {[key: string]: any;} const [employee, setEmployee] = useState<Employee>({}); useEffect(() => { setEmployee({ name: 'Alice', salary: 100, department: 'Dev', tasks: ['dev', 'test', 'ship'], }); }, []); return ( <div> <h2>Name: {employee.name}</h2> <h2>Salary: {employee.salary}</h2> </div> ); }; export default App;
The code for this article is available on GitHub

We used a union to set the age property to be of type number or string.

You can repeat this process to include as many types as necessary.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev