Lesson 56 of 60 – Typing useState
93%

Typing useState

The useState Hook is used in React to store and update component state. When using TypeScript, we can provide a type for the state so that only valid values can be stored.

Note: TypeScript can often infer the type of a simple useState value automatically. For more complex or initially empty states, providing an explicit type is useful.

1. What is useState?

useState is a React Hook that allows a functional component to store state.

import { useState } from "react";

function App() {

    const [count, setCount] = useState(0);

    return (
        <h2>{count}</h2>
    );
}

2. Importing useState

Before using useState, import it from React.

import { useState } from "react";

This gives the component access to the useState Hook.

3. Basic useState Syntax

const [state, setState] = useState(initialValue);
  • state stores the current value.
  • setState updates the value.
  • initialValue is the starting value.

4. TypeScript Type Inference with useState

TypeScript can infer the type from the initial value.

const [count, setCount] = useState(0);

TypeScript understands that count is a number.

5. Explicit Number State

We can explicitly specify the state type using a generic.

const [count, setCount] = useState<number>(0);

The state can now contain only numbers.

6. Updating Number State

const [count, setCount] = useState<number>(0);

setCount(10);

The setCount function updates the state.

7. String State

const [name, setName] = useState<string>("");

setName("Rahul");

The state is restricted to string values.

8. Boolean State

const [isLoggedIn, setIsLoggedIn] =
    useState<boolean>(false);

setIsLoggedIn(true);

Boolean state is useful for values such as login status, loading state, and visibility.

9. Array State

const [names, setNames] =
    useState<string[]>([]);

setNames(["Rahul", "Amit", "Priya"]);

The state above can contain an array of strings.

10. Number Array State

const [marks, setMarks] =
    useState<number[]>([]);

setMarks([80, 75, 90]);

Every item in the array must be a number.

11. Object State

interface User {
    name: string;
    age: number;
}

const [user, setUser] =
    useState<User>({
        name: "Rahul",
        age: 20
    });

An interface can describe the structure of an object state.

12. Updating Object State

setUser({
    name: "Amit",
    age: 22
});

The new object must follow the User interface.

13. Updating One Object Property

The spread operator can preserve the other properties.

setUser({
    ...user,
    age: 21
});

Only the age property is changed in the new state object.

14. State with Union Types

A state can accept more than one type using a union type.

const [value, setValue] =
    useState<string | number>("");

setValue("Hello");
setValue(100);

15. Nullable State

Sometimes state starts with null and later receives an object.

interface User {
    name: string;
}

const [user, setUser] =
    useState<User | null>(null);

The state can contain either a User object or null.

16. Checking Nullable State

if (user) {
    console.log(user.name);
}

The condition ensures that user is not null before accessing its properties.

17. Empty Array with an Explicit Type

When an array starts empty, providing a type can clearly describe what it will contain.

const [students, setStudents] =
    useState<string[]>([]);

18. Adding Items to Array State

setStudents([
    ...students,
    "Rahul"
]);

The spread operator creates a new array containing the existing students and the new student.

19. Removing Items from Array State

setStudents(
    students.filter(
        student => student !== "Rahul"
    )
);

filter() can create a new array without the selected item.

20. Functional State Updates

When the next state depends on the previous state, use the functional update form.

setCount(previousCount =>
    previousCount + 1
);

This is useful when updating state based on its previous value.

21. Multiple State Variables

const [name, setName] =
    useState<string>("");

const [age, setAge] =
    useState<number>(0);

const [active, setActive] =
    useState<boolean>(false);

A component can have multiple state variables.

22. useState with Input

const [name, setName] =
    useState<string>("");

return (
    <input
        value={name}
        onChange={(event) =>
            setName(event.target.value)
        }
    />
);

The state can store the current value of an input field.

23. useState with a Counter

import { useState } from "react";

function Counter() {

    const [count, setCount] =
        useState<number>(0);

    return (
        <div>

            <h2>Count: {count}</h2>

            <button
                onClick={() =>
                    setCount(
                        previousCount =>
                        previousCount + 1
                    )
                }
            >
                Increase
            </button>

        </div>
    );
}

24. useState with Loading State

const [loading, setLoading] =
    useState<boolean>(false);

setLoading(true);

// perform operation

setLoading(false);

Boolean state is commonly used to represent loading status.

25. useState with API Data

interface Product {
    id: number;
    name: string;
    price: number;
}

const [products, setProducts] =
    useState<Product[]>([]);

This pattern is useful when storing typed API data in React state.

26. Common useState Typing Mistakes

  • Using the wrong state type.
  • Forgetting a type for an initially empty array.
  • Using an object that does not match its interface.
  • Updating an array or object incorrectly.
  • Ignoring null when the state can be null.
  • Using a direct state value when a functional update is more appropriate.

27. When Should You Add an Explicit Type?

TypeScript inference is often enough for simple state.

const [count, setCount] = useState(0);

An explicit type is especially useful when the initial value does not fully describe the future state.

const [user, setUser] =
    useState<User | null>(null);

28. useState in React Native

The same TypeScript patterns can be used with React Native.

import { useState } from "react";
import { Text, Button, View } from "react-native";

function Counter() {

    const [count, setCount] =
        useState<number>(0);

    return (
        <View>

            <Text>
                Count: {count}
            </Text>

            <Button
                title="Increase"
                onPress={() =>
                    setCount(
                        previousCount =>
                        previousCount + 1
                    )
                }
            />

        </View>
    );
}

29. Best Practices for Typed State

  • Use simple inferred types when they are clear.
  • Use interfaces for complex object state.
  • Use explicit types for nullable state.
  • Type arrays when their item structure matters.
  • Use functional updates when the new value depends on the previous value.
  • Avoid unnecessary type annotations when TypeScript already infers the correct type.
  • Keep state types simple and meaningful.

30. Complete Typed useState Example

import { useState } from "react";

interface Student {
    id: number;
    name: string;
    course: string;
}

function StudentApp() {

    const [student, setStudent] =
        useState<Student | null>(null);

    const [students, setStudents] =
        useState<Student[]>([]);

    const [loading, setLoading] =
        useState<boolean>(false);

    const addStudent = () => {

        const newStudent: Student = {
            id: 101,
            name: "Rahul",
            course: "React Native"
        };

        setStudent(newStudent);

        setStudents(previousStudents => [
            ...previousStudents,
            newStudent
        ]);
    };

    return (
        <div>

            <h2>Student Management</h2>

            <button
                onClick={addStudent}
                disabled={loading}
            >
                Add Student
            </button>

            {student && (
                <div>

                    <p>
                        ID: {student.id}
                    </p>

                    <p>
                        Name: {student.name}
                    </p>

                    <p>
                        Course: {student.course}
                    </p>

                </div>
            )}

            <p>
                Total Students: {students.length}
            </p>

        </div>
    );
}

export default StudentApp;

This example combines object state, nullable state, array state, boolean state, interfaces, and functional state updates.

📌 Key Points

  • useState is used to manage component state.
  • TypeScript can often infer simple state types automatically.
  • Use useState<type>() when an explicit type is useful.
  • String, number, boolean, array, and object states can all be typed.
  • Use interfaces for complex object state.
  • Use union types such as User | null for nullable state.
  • Use functional updates when the next state depends on the previous state.
  • Typed state is especially useful for larger React and React Native applications.

🧠 Quick Quiz

Question: Which syntax explicitly tells TypeScript that a useState value is a number?