React Dasar

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 ini
  • setState: Function untuk mengupdate state
  • initialValue: 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:

src/pages/Home/Home.jsx
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;
src/pages/Home/Home.css
.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:

src/pages/Home/Home.jsx (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:

  1. Hanya call Hooks di top level - Jangan di dalam loops, conditions, atau nested functions
  2. 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:

src/App.jsx
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:

  1. Tambahkan beberapa tasks
  2. Tandai task sebagai selesai
  3. Hapus task
  4. Refresh halaman - tasks tetap tersimpan!

Best Practices

  1. Keep state as local as possible - Hanya lift state up jika benar-benar perlu
  2. Use meaningful state names - isLoading lebih baik dari loading
  3. Don't duplicate state - Derive dari state yang ada jika bisa
  4. Minimize useEffect dependencies - Hanya tambahkan yang benar-benar digunakan
  5. Always cleanup side effects - Return cleanup function di useEffect

Latihan

  1. Tambahkan filter tasks (All, Active, Completed)
  2. Implementasikan search/filter by title
  3. Tambahkan sort by date atau alphabetical
  4. Buat counter yang auto-increment setiap detik menggunakan useEffect

Apa Selanjutnya?

Selanjutnya kita akan belajar React Router untuk membuat aplikasi multi-halaman!