Skip to main content

State and useState in TypeScript

Level: Beginner to Intermediate

ℹ️ What You'll Learn
  • What State and useState in TypeScript means in React + TypeScript
  • How type safety improves React components
  • How to model School Management System data with interfaces and types
  • Common TypeScript mistakes to avoid
  • How to explain this topic in interviews

Why This Matters

State and useState in TypeScript helps you build React screens with fewer runtime bugs. In real .NET Web API projects, TypeScript makes API DTOs, component props, form state, and shared data contracts easier to understand and safer to change.

TypeScript makes state safe by requiring type declarations.

The Problem

React JavaScript can fail at runtime when props, API responses, or form values have the wrong shape. This lesson shows how State and useState in TypeScript uses TypeScript to catch many of those mistakes while you write code, before the student dashboard reaches users.

Basic useState Typing

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

// Type inference (TypeScript guesses type)
const [name, setName] = useState('Ravi'); // inferred as string

// Union type for optional
const [student, setStudent] = useState<Student | null>(null);

// Array of objects
const [students, setStudents] = useState<Student[]>([]);

// Object state
interface FormData {
name: string;
email: string;
className: string;
}

const [form, setForm] = useState<FormData>({
name: '',
email: '',
className: ''
});

Complex State Patterns

Async State with Loading/Error

interface AsyncState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}

function StudentDetail({ studentId }: { studentId: number }) {
const [state, setState] = useState<AsyncState<Student>>({
data: null,
loading: true,
error: null
});

useEffect(() => {
fetch(`/api/students/${studentId}`)
.then(r => r.json())
.then(data => setState({ data, loading: false, error: null }))
.catch(error => setState({ data: null, loading: false, error }));
}, [studentId]);

if (state.loading) return <p>Loading...</p>;
if (state.error) return <p>Error: {state.error.message}</p>;
return <div>{state.data?.name}</div>;
}

Union State

type StudentState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'error'; error: Error }
| { status: 'success'; data: Student };

function StudentDetail() {
const [state, setState] = useState<StudentState>({ status: 'idle' });

if (state.status === 'loading') return <p>Loading...</p>;
if (state.status === 'error') return <p>{state.error.message}</p>;
if (state.status === 'success') return <p>{state.data.name}</p>;
return null;
}

Updater Functions

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

// Updater function style (safer)
setCount((prev: number) => prev + 1);

// Type inference works
setCount(prev => prev + 1); // TypeScript infers prev is number

Form State Typing

interface StudentForm {
name: string;
email: string;
className: string;
marks: number;
isActive: boolean;
}

function StudentForm() {
const [form, setForm] = useState<StudentForm>({
name: '',
email: '',
className: '10',
marks: 0,
isActive: true
});

const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;

setForm(prev => ({
...prev,
[name]: type === 'number' ? parseFloat(value) : value
}));
};

return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="marks" type="number" value={form.marks} onChange={handleChange} />
</form>
);
}

Enum State

enum StudentStatus {
Active = 'Active',
Inactive = 'Inactive',
Graduated = 'Graduated'
}

function StudentFilter() {
const [status, setStatus] = useState<StudentStatus>(StudentStatus.Active);

return (
<select value={status} onChange={e => setStatus(e.target.value as StudentStatus)}>
<option value={StudentStatus.Active}>Active</option>
<option value={StudentStatus.Inactive}>Inactive</option>
<option value={StudentStatus.Graduated}>Graduated</option>
</select>
);
}

Discriminated Union State

type ViewState =
| { view: 'list'; students: Student[] }
| { view: 'detail'; student: Student }
| { view: 'error'; message: string };

function StudentView() {
const [state, setState] = useState<ViewState>({ view: 'list', students: [] });

if (state.view === 'list') {
return <ul>{state.students.map(s => <li key={s.id}>{s.name}</li>)}</ul>;
}

if (state.view === 'detail') {
return <div>{state.student.name}</div>;
}

if (state.view === 'error') {
return <p>{state.message}</p>;
}
}

Key Takeaways

  • Use useState<T>() for type safety
  • Type inference reduces boilerplate
  • Complex states need interfaces
  • Union types for different states
  • Discriminated unions for clarity
  • Next: useEffect with types
⚠️ State Typing Mistakes
  1. Not typing state — Lose type checking
  2. Using any — Defeats TypeScript
  3. Wrong type on updater — State becomes wrong type
  4. Forgetting union — State can be multiple types
💡 Type Inference

TypeScript often infers types, so you don't always need explicit types:

// TypeScript infers type from initial value
const [count, setCount] = useState(0); // inferred: number
const [active, setActive] = useState(true); // inferred: boolean
const [students, setStudents] = useState<Student[]>([]); // explicit (empty array can't infer)
🤖Use AI to Learn Faster

Use ChatGPT, Claude, or Copilot to go deeper on Typed State. Try these prompts:

  • "Why type useState state?"
  • "What's the difference between useState<T | null> and optional?"
  • "When would you use discriminated unions?"
  • "Quiz me on TypeScript state"

💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.

Quick Definitions

  • State and useState in TypeScript - The main React + TypeScript concept explained in this lesson.
  • Type/interface - A contract that describes the shape of data.
  • Typed props/state - React data with clear compile-time expectations.
  • API DTO - The request or response shape shared with ASP.NET Core Web API.

Common Mistakes

  • Using any too quickly instead of defining a useful type
  • Typing props loosely and losing the benefit of TypeScript
  • Forgetting that API data can still be missing or invalid at runtime
  • Making types too complex before the component is stable
  • Not sharing clear DTO shapes between frontend and backend teams

Practice Task

Create a small React TypeScript example using State and useState in TypeScript. Keep it connected to a School Management System scenario.

Suggested practice:

  1. Define a clear Student, Teacher, or Attendance type.
  2. Build a small typed component or hook.
  3. Add one valid example and one intentionally wrong example to see TypeScript errors.
  4. Explain the type contract in your own words.
  5. Rebuild the same example once without looking at the article.

Quick Revision

QuestionAnswer
What is the main idea?Use TypeScript to make State and useState in TypeScript safer in React.
Where is it used?Props, state, forms, API responses, context, hooks, and routes.
What should beginners avoid?Overusing any and ignoring runtime API validation.
What is the best debugging habit?Read the TypeScript error, check the data shape, and fix the type contract.
🎯 How would you explain State and useState in TypeScript in an interview?

State and useState in TypeScript is a React + TypeScript concept that improves safety and maintainability. I would explain which values need types, how TypeScript catches mistakes early, and how it helps when consuming ASP.NET Core Web API responses.

🎯 Where is this used in a real React TypeScript project?

It is used in typed props, API response models, form state, route parameters, context values, custom hooks, and reusable UI components.

Next Article

Styling React Components in TypeScript ->

nexcoding.in