State & Hooks
Mengelola state dengan useState dan side effects dengan useEffect
Apa itu State?
State adalah data yang dikelola di dalam component dan dapat berubah sepanjang lifecycle component. Ketika state berubah, React secara otomatis me-render ulang component.
Props vs State
Props dikirim dari parent dan read-only, sedangkan State dikelola di dalam component dan dapat diubah. State lokal ke component, props datang dari luar.
useState Hook
useState adalah Hook yang memungkinkan kita menambahkan state ke functional component.
Sintaks Dasar
const [state, setState] = useState(initialValue);state: Nilai state saat inisetState: Function untuk mengupdate stateinitialValue: Nilai awal state
Contoh Penggunaan
import React, { useState } from 'react';
function NameInput() {
const [name, setName] = useState('');
return (
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Masukkan nama"
/>
);
}import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}import React, { useState } from 'react';
function ToggleSwitch() {
const [isOn, setIsOn] = useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'ON' : 'OFF'}
</button>
);
}import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text }]);
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>
Delete
</button>
</div>
))}
</div>
);
}import React, { useState } from 'react';
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0
});
const updateField = (field, value) => {
setUser({ ...user, [field]: value });
};
return (
<div>
<input
value={user.name}
onChange={(e) => updateField('name', e.target.value)}
/>
<input
value={user.email}
onChange={(e) => updateField('email', e.target.value)}
/>
</div>
);
}Membuat Home Page dengan State
Mari membuat halaman utama aplikasi Task Manager yang mengelola state tasks:
import React, { useState } from 'react';
import Header from '../../components/Header/Header';
import TaskForm from '../../components/TaskForm/TaskForm';
import TaskItem from '../../components/TaskItem/TaskItem';
import './Home.css';
function Home() {
const [tasks, setTasks] = useState([]);
const handleAddTask = (newTask) => {
setTasks([...tasks, newTask]);
};
const handleDeleteTask = (taskId) => {
setTasks(tasks.filter(task => task.id !== taskId));
};
const handleToggleComplete = (taskId) => {
setTasks(tasks.map(task =>
task.id === taskId ? { ...task, completed: !task.completed } : task
));
};
const completedTasks = tasks.filter(task => task.completed).length;
const remainingTasks = tasks.length - completedTasks;
return (
<div className="home">
<Header
title="React Task Manager"
description="Kelola tugas Anda dengan mudah"
/>
<main className="container">
<div className="stats">
<p>Total: {tasks.length} tugas</p>
<p>Selesai: {completedTasks}</p>
<p>Belum selesai: {remainingTasks}</p>
</div>
<TaskForm onAddTask={handleAddTask} />
<div className="task-list">
{tasks.length === 0 ? (
<p className="empty-message">Belum ada tugas. Tambahkan tugas baru!</p>
) : (
tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onDelete={handleDeleteTask}
onToggleComplete={handleToggleComplete}
/>
))
)}
</div>
</main>
</div>
);
}
export default Home;.container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.stats {
display: flex;
justify-content: space-between;
background-color: #f1f8ff;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1.5rem;
font-size: 0.9rem;
}
.task-list {
margin-top: 1rem;
}
.empty-message {
text-align: center;
padding: 2rem;
background-color: #f8f9fa;
border-radius: 4px;
color: #6c757d;
}Updating State dengan Object/Array
Saat mengupdate state yang berupa object atau array, selalu buat copy baru menggunakan spread operator (...). Jangan mutate state langsung!
// Salah - mutate langsung
tasks.push(newTask);
setTasks(tasks);
// Benar - buat array baru
setTasks([...tasks, newTask]);useEffect Hook
useEffect adalah Hook untuk melakukan side effects di functional component, seperti:
- Data fetching
- Subscriptions
- Manual DOM manipulation
- localStorage operations
Sintaks Dasar
useEffect(() => {
// Side effect code here
return () => {
// Cleanup code (optional)
};
}, [dependencies]);Variasi useEffect
// Hanya run sekali saat component mount
useEffect(() => {
console.log('Component mounted');
// Fetch initial data, setup subscriptions, etc.
}, []); // Empty dependency array// Run setiap kali count berubah
useEffect(() => {
console.log('Count changed:', count);
document.title = `Count: ${count}`;
}, [count]); // Depends on count// Run setiap component re-render (jarang digunakan)
useEffect(() => {
console.log('Component rendered');
}); // No dependency array// With cleanup function
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
};
}, []);localStorage dengan useEffect
Mari tambahkan persistensi data dengan localStorage:
import React, { useState, useEffect } from 'react';
import Header from '../../components/Header/Header';
import TaskForm from '../../components/TaskForm/TaskForm';
import TaskItem from '../../components/TaskItem/TaskItem';
import './Home.css';
function Home() {
const [tasks, setTasks] = useState([]);
// Load tasks dari localStorage saat component mount
useEffect(() => {
const savedTasks = localStorage.getItem('tasks');
if (savedTasks) {
setTasks(JSON.parse(savedTasks));
}
}, []);
// Simpan tasks ke localStorage setiap kali tasks berubah
useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(tasks));
}, [tasks]);
const handleAddTask = (newTask) => {
setTasks([...tasks, newTask]);
};
const handleDeleteTask = (taskId) => {
setTasks(tasks.filter(task => task.id !== taskId));
};
const handleToggleComplete = (taskId) => {
setTasks(tasks.map(task =>
task.id === taskId ? { ...task, completed: !task.completed } : task
));
};
const completedTasks = tasks.filter(task => task.completed).length;
const remainingTasks = tasks.length - completedTasks;
return (
<div className="home">
<Header
title="React Task Manager"
description="Kelola tugas Anda dengan mudah"
/>
<main className="container">
<div className="stats">
<p>Total: {tasks.length} tugas</p>
<p>Selesai: {completedTasks}</p>
<p>Belum selesai: {remainingTasks}</p>
</div>
<TaskForm onAddTask={handleAddTask} />
<div className="task-list">
{tasks.length === 0 ? (
<p className="empty-message">Belum ada tugas. Tambahkan tugas baru!</p>
) : (
tasks.map(task => (
<TaskItem
key={task.id}
task={task}
onDelete={handleDeleteTask}
onToggleComplete={handleToggleComplete}
/>
))
)}
</div>
</main>
</div>
);
}
export default Home;Dependency Array
Dependency array [tasks] memberitahu React untuk run effect hanya ketika tasks berubah. Ini penting untuk menghindari infinite loop dan optimization performance.
Multiple State Variables
Kalian bisa menggunakan multiple useState dalam satu component:
function MyComponent() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [isActive, setIsActive] = useState(false);
const [todos, setTodos] = useState([]);
// ...
}Atau gunakan satu object state:
function MyComponent() {
const [user, setUser] = useState({
name: '',
age: 0,
isActive: false,
todos: []
});
// Update specific field
setUser({ ...user, name: 'John' });
}State Update adalah Asynchronous
State update di React adalah asynchronous. Jika Kalian butuh nilai state yang ter-update, gunakan functional update:
// Bisa error jika dipanggil multiple kali cepat
setCount(count + 1);
// Selalu aman
setCount(prevCount => prevCount + 1);Rules of Hooks
Hooks memiliki aturan yang harus diikuti:
- Hanya call Hooks di top level - Jangan di dalam loops, conditions, atau nested functions
- Hanya call Hooks dari React functions - Dari functional components atau custom Hooks
// Salah - Hook di dalam condition
function MyComponent() {
if (condition) {
const [state, setState] = useState(0); // Error!
}
}
// Benar - Hook di top level
function MyComponent() {
const [state, setState] = useState(0);
if (condition) {
// Use state here
}
}Update App.jsx
Update App.jsx untuk menggunakan Home component:
import React from 'react';
import Home from './pages/Home/Home';
import './App.css';
function App() {
return (
<div className="App">
<Home />
</div>
);
}
export default App;Test Aplikasi
Jalankan npm start dan coba:
- Tambahkan beberapa tasks
- Tandai task sebagai selesai
- Hapus task
- Refresh halaman - tasks tetap tersimpan!
Best Practices
- Keep state as local as possible - Hanya lift state up jika benar-benar perlu
- Use meaningful state names -
isLoadinglebih baik dariloading - Don't duplicate state - Derive dari state yang ada jika bisa
- Minimize useEffect dependencies - Hanya tambahkan yang benar-benar digunakan
- Always cleanup side effects - Return cleanup function di useEffect
Latihan
- Tambahkan filter tasks (All, Active, Completed)
- Implementasikan search/filter by title
- Tambahkan sort by date atau alphabetical
- Buat counter yang auto-increment setiap detik menggunakan useEffect
Apa Selanjutnya?
Selanjutnya kita akan belajar React Router untuk membuat aplikasi multi-halaman!
