Home Quizzes Leaderboard Competitions Learn Hire Us
About Contact
Log In Sign Up
Learn Next.js State & Client Interactivity

State & Client Interactivity

⏱ 15 min read read
useState --- Client Component State:

State lives in Client Components ('use client'). Every state change
triggers a re-render of that component.

'use client';

import { useState } from 'react';

// const [value, setter] = useState(initialValue)

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

const [name, setName] = useState('');

const [isOpen, setIsOpen] = useState(false);

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

Controlled Inputs:

const [email, setEmail] = useState('');

<input

type="email"

value={email}

onChange={(e) => setEmail(e.target.value)}

placeholder="Enter email"

/>

Updating Arrays and Objects in State:

// NEVER mutate state directly --- always create new arrays/objects

// Add item

setStudents(prev => [...prev, newStudent]);

// Remove item

setStudents(prev => prev.filter(s => s.id !== id));

// Update item

setStudents(prev => prev.map(s => s.id === id ? { ...s, grade } :
s));

// Update object field

setForm(prev => ({ ...prev, email: 'new@email.com' }));

useReducer --- For Complex State:

type Action = { type: 'ADD'; payload: Student }

| { type: 'REMOVE'; payload: number }

| { type: 'RESET' };

function reducer(state: Student[], action: Action): Student[] {

switch (action.type) {

case 'ADD': return [...state, action.payload];

case 'REMOVE': return state.filter(s => s.id !== action.payload);

case 'RESET': return [];

}

}

const [students, dispatch] = useReducer(reducer, []);

dispatch({ type: 'ADD', payload: newStudent });
Code Example
'use client';

import { useState } from 'react';

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

export default function StudentManager() {

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

{ id: 1, name: 'Alice', grade: 92 },

{ id: 2, name: 'Bob', grade: 78 },

]);

const [name, setName] = useState('');

const [grade, setGrade] = useState('');

const [error, setError] = useState('');

const addStudent = () => {

const g = Number(grade);

if (!name.trim()) { setError('Name required'); return; }

if (isNaN(g) || g < 0 || g > 100) { setError('Grade 0--100');
return; }

setStudents(prev => [...prev, { id: Date.now(), name: name.trim(),
grade: g }]);

setName('');

setGrade('');

setError('');

};

const removeStudent = (id: number) => {

setStudents(prev => prev.filter(s => s.id !== id));

};

const avg = students.length

? (students.reduce((s, x) => s + x.grade, 0) /
students.length).toFixed(1)

: '---';

return (

<div>

<h1>Student Manager</h1>

<p>Class average: {avg}</p>

<div>

<input value={name} onChange={e => setName(e.target.value)}
placeholder="Name" />

<input value={grade} onChange={e => setGrade(e.target.value)}
placeholder="Grade" type="number" />

<button onClick={addStudent}>Add</button>

{error && <p style={{ color: 'red' }}>{error}</p>}

</div>

<ul>

{students.map(s => (

<li key={s.id}>

{s.name}: {s.grade}

<button onClick={() => removeStudent(s.id)}>✕</button>

</li>

))}

</ul>

</div>

);

}
← Components, Props & TypeScript Hooks --- useEffect, useContext & Custom →

Log in to track your progress and earn badges as you complete lessons.

Log In to Track Progress