Kuasai React dari dasar. Pelajari mengapa React diciptakan, pahami core concepts seperti components, JSX, state, props, hooks, dan lifecycle. Bangun complete production-ready task management app yang cover semua React fundamentals dengan best practices.

React telah menjadi dominant frontend framework untuk building interactive user interfaces. Tapi mengapa React ada, dan apa yang membuatnya powerful? Understanding React's philosophy dan core concepts adalah essential untuk building scalable, maintainable web applications.
Dalam artikel ini, kita akan explore React's history, understand mengapa React diciptakan, dive deep ke core concepts, dan build complete production-ready task management application yang demonstrate semua fundamental React patterns.
Sebelum React, building interactive web applications sangat challenging:
Facebook created React di 2011 untuk solve problems ini:
Components adalah building blocks dari React applications. Mereka reusable, encapsulated pieces dari UI.
// ✅ Modern approach - Function Components
const Welcome = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};
// Usage
<Welcome name="Alice" />JSX adalah syntax extension yang looks like HTML tapi actually JavaScript.
// JSX
const element = <h1 className="greeting">Hello World</h1>;
// Compiles to
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello World'
);Props adalah how you pass data dari parent ke child components. Mereka immutable.
const Button = ({ label, onClick, disabled }) => {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
};
// Usage
<Button label="Click me" onClick={() => alert('Clicked')} disabled={false} />State adalah data yang changes over time. Components re-render ketika state changes.
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};Hooks let you use state dan other React features dalam function components.
import { useState, useEffect, useContext, useReducer } from 'react';
// useState - manage state
const [state, setState] = useState(initialValue);
// useEffect - side effects
useEffect(() => {
// Run after render
return () => {
// Cleanup
};
}, [dependencies]);
// useContext - access context
const value = useContext(MyContext);
// useReducer - complex state logic
const [state, dispatch] = useReducer(reducer, initialState);Render different UI based on conditions.
const LoginStatus = ({ isLoggedIn }) => {
// Ternary operator
return isLoggedIn ? <Dashboard /> : <LoginForm />;
};
const Alert = ({ type, message }) => {
// Early return
if (!message) return null;
return <div className={`alert alert-${type}`}>{message}</div>;
};
const List = ({ items }) => {
// Logical AND
return items.length > 0 && <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
};Render lists efficiently dengan proper keys.
const TodoList = ({ todos }) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.title}
</li>
))}
</ul>
);
};
// ❌ Wrong - using index as key
{items.map((item, index) => <li key={index}>{item}</li>)}
// ✅ Correct - using unique identifier
{items.map((item) => <li key={item.id}>{item}</li>)}Handle user interactions dengan event handlers.
const Form = () => {
const handleClick = () => {
console.log('Button clicked');
};
const handleChange = (e) => {
console.log('Input value:', e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
};Manage form inputs dengan controlled components.
const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('Login:', { email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
};Handle side effects seperti API calls, subscriptions, dan timers.
import { useState, useEffect } from 'react';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch user data
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('Error fetching user:', error);
} finally {
setLoading(false);
}
};
fetchUser();
// Cleanup function
return () => {
// Cancel requests, unsubscribe, etc.
};
}, [userId]); // Dependency array
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
};Mari kita build complete task management application yang demonstrate semua React fundamentals.
task-manager/
├── src/
│ ├── components/
│ │ ├── TaskForm.tsx
│ │ ├── TaskList.tsx
│ │ ├── TaskItem.tsx
│ │ └── TaskFilter.tsx
│ ├── hooks/
│ │ └── useTasks.ts
│ ├── types/
│ │ └── task.ts
│ ├── App.tsx
│ └── App.css
├── package.json
└── vite.config.tsexport interface Task {
id: string;
title: string;
description: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
dueDate: string;
createdAt: Date;
}
export type FilterType = 'all' | 'active' | 'completed';import { useState, useCallback } from 'react';
import { Task } from '../types/task';
export const useTasks = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const addTask = useCallback((task: Omit<Task, 'id' | 'createdAt'>) => {
const newTask: Task = {
...task,
id: Date.now().toString(),
createdAt: new Date(),
};
setTasks((prev) => [newTask, ...prev]);
return newTask;
}, []);
const updateTask = useCallback((id: string, updates: Partial<Task>) => {
setTasks((prev) =>
prev.map((task) => (task.id === id ? { ...task, ...updates } : task))
);
}, []);
const deleteTask = useCallback((id: string) => {
setTasks((prev) => prev.filter((task) => task.id !== id));
}, []);
const toggleTask = useCallback((id: string) => {
updateTask(id, { completed: !tasks.find((t) => t.id === id)?.completed });
}, [tasks, updateTask]);
return { tasks, addTask, updateTask, deleteTask, toggleTask };
};import { useState } from 'react';
import { Task } from '../types/task';
interface TaskFormProps {
onSubmit: (task: Omit<Task, 'id' | 'createdAt'>) => void;
}
export const TaskForm = ({ onSubmit }: TaskFormProps) => {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
const [dueDate, setDueDate] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!title.trim()) {
alert('Please enter a task title');
return;
}
onSubmit({
title,
description,
priority,
dueDate,
completed: false,
});
// Reset form
setTitle('');
setDescription('');
setPriority('medium');
setDueDate('');
};
return (
<form onSubmit={handleSubmit} className="task-form">
<div className="form-group">
<label htmlFor="title">Task Title *</label>
<input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter task title"
className="form-input"
/>
</div>
<div className="form-group">
<label htmlFor="description">Description</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Enter task description"
className="form-input"
rows={3}
/>
</div>
<div className="form-row">
<div className="form-group">
<label htmlFor="priority">Priority</label>
<select
id="priority"
value={priority}
onChange={(e) => setPriority(e.target.value as any)}
className="form-input"
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<div className="form-group">
<label htmlFor="dueDate">Due Date</label>
<input
id="dueDate"
type="date"
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
className="form-input"
/>
</div>
</div>
<button type="submit" className="btn btn-primary">
Add Task
</button>
</form>
);
};import { Task } from '../types/task';
interface TaskItemProps {
task: Task;
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export const TaskItem = ({ task, onToggle, onDelete }: TaskItemProps) => {
const priorityColor = {
low: '#3b82f6',
medium: '#f59e0b',
high: '#ef4444',
};
return (
<div className={`task-item ${task.completed ? 'completed' : ''}`}>
<div className="task-content">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
className="task-checkbox"
/>
<div className="task-details">
<h3 className="task-title">{task.title}</h3>
{task.description && <p className="task-description">{task.description}</p>}
<div className="task-meta">
<span
className="priority-badge"
style={{ backgroundColor: priorityColor[task.priority] }}
>
{task.priority}
</span>
{task.dueDate && (
<span className="due-date">
Due: {new Date(task.dueDate).toLocaleDateString()}
</span>
)}
</div>
</div>
</div>
<button
onClick={() => onDelete(task.id)}
className="btn btn-danger btn-small"
>
Delete
</button>
</div>
);
};import { FilterType } from '../types/task';
interface TaskFilterProps {
activeFilter: FilterType;
onFilterChange: (filter: FilterType) => void;
stats: {
total: number;
active: number;
completed: number;
};
}
export const TaskFilter = ({ activeFilter, onFilterChange, stats }: TaskFilterProps) => {
const filters: FilterType[] = ['all', 'active', 'completed'];
return (
<div className="task-filter">
<div className="filter-buttons">
{filters.map((filter) => (
<button
key={filter}
onClick={() => onFilterChange(filter)}
className={`filter-btn ${activeFilter === filter ? 'active' : ''}`}
>
{filter.charAt(0).toUpperCase() + filter.slice(1)}
</button>
))}
</div>
<div className="filter-stats">
<span>Total: {stats.total}</span>
<span>Active: {stats.active}</span>
<span>Completed: {stats.completed}</span>
</div>
</div>
);
};import { Task, FilterType } from '../types/task';
import { TaskItem } from './TaskItem';
interface TaskListProps {
tasks: Task[];
filter: FilterType;
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export const TaskList = ({ tasks, filter, onToggle, onDelete }: TaskListProps) => {
const filteredTasks = tasks.filter((task) => {
if (filter === 'active') return !task.completed;
if (filter === 'completed') return task.completed;
return true;
});
if (filteredTasks.length === 0) {
return (
<div className="empty-state">
<p>No tasks found. {filter !== 'all' && `Try changing the filter.`}</p>
</div>
);
}
return (
<div className="task-list">
{filteredTasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</div>
);
};import { useState, useMemo } from 'react';
import { TaskForm } from './components/TaskForm';
import { TaskList } from './components/TaskList';
import { TaskFilter } from './components/TaskFilter';
import { useTasks } from './hooks/useTasks';
import { FilterType } from './types/task';
import './App.css';
function App() {
const { tasks, addTask, deleteTask, toggleTask } = useTasks();
const [filter, setFilter] = useState<FilterType>('all');
const stats = useMemo(() => {
return {
total: tasks.length,
active: tasks.filter((t) => !t.completed).length,
completed: tasks.filter((t) => t.completed).length,
};
}, [tasks]);
return (
<div className="app">
<header className="app-header">
<h1>Task Manager</h1>
<p>Organize your tasks efficiently</p>
</header>
<main className="app-main">
<div className="container">
<TaskForm onSubmit={addTask} />
<TaskFilter
activeFilter={filter}
onFilterChange={setFilter}
stats={stats}
/>
<TaskList
tasks={tasks}
filter={filter}
onToggle={toggleTask}
onDelete={deleteTask}
/>
</div>
</main>
</div>
);
}
export default App;* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.app {
max-width: 800px;
margin: 0 auto;
}
.app-header {
text-align: center;
color: white;
margin-bottom: 40px;
}
.app-header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.app-header p {
font-size: 1.1rem;
opacity: 0.9;
}
.container {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.task-form {
margin-bottom: 30px;
padding-bottom: 30px;
border-bottom: 2px solid #e5e7eb;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #374151;
}
.form-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #667eea;
color: white;
width: 100%;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.btn-small {
padding: 6px 12px;
font-size: 0.875rem;
}
.task-filter {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding: 15px;
background: #f9fafb;
border-radius: 8px;
}
.filter-buttons {
display: flex;
gap: 10px;
}
.filter-btn {
padding: 8px 16px;
border: 2px solid #e5e7eb;
background: white;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
}
.filter-btn.active {
border-color: #667eea;
background: #667eea;
color: white;
}
.filter-stats {
display: flex;
gap: 20px;
font-size: 0.875rem;
color: #6b7280;
}
.task-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f9fafb;
border-radius: 8px;
border-left: 4px solid #667eea;
transition: all 0.2s;
}
.task-item:hover {
background: #f3f4f6;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.task-item.completed {
opacity: 0.6;
border-left-color: #10b981;
}
.task-item.completed .task-title {
text-decoration: line-through;
color: #9ca3af;
}
.task-content {
display: flex;
gap: 12px;
flex: 1;
}
.task-checkbox {
width: 20px;
height: 20px;
cursor: pointer;
margin-top: 2px;
}
.task-details {
flex: 1;
}
.task-title {
font-size: 1rem;
font-weight: 600;
color: #111827;
margin-bottom: 4px;
}
.task-description {
font-size: 0.875rem;
color: #6b7280;
margin-bottom: 8px;
}
.task-meta {
display: flex;
gap: 10px;
align-items: center;
}
.priority-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
color: white;
font-size: 0.75rem;
font-weight: 600;
}
.due-date {
font-size: 0.875rem;
color: #6b7280;
}
.empty-state {
text-align: center;
padding: 40px 20px;
color: #9ca3af;
}
@media (max-width: 640px) {
.form-row {
grid-template-columns: 1fr;
}
.task-filter {
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.filter-stats {
width: 100%;
justify-content: space-around;
}
.task-item {
flex-direction: column;
align-items: flex-start;
}
.btn-danger {
width: 100%;
}
}// ✅ Keep components small dan focused
// ✅ Use meaningful component names
// ✅ Separate container dan presentational components
// ✅ Lift state up ketika needed
// ✅ Use composition over inheritance// ✅ Keep state as local as possible
// ✅ Use custom hooks untuk shared logic
// ✅ Avoid prop drilling dengan Context API
// ✅ Use useReducer untuk complex state
// ✅ Memoize expensive computations// ✅ Use React.memo untuk expensive components
// ✅ Use useCallback untuk event handlers
// ✅ Use useMemo untuk expensive calculations
// ✅ Lazy load components dengan React.lazy
// ✅ Use key prop correctly dalam lists// ✅ Group related files together
// ✅ Use consistent naming conventions
// ✅ Extract reusable logic ke hooks
// ✅ Keep components dalam separate files
// ✅ Use TypeScript untuk type safety// ✅ Test component behavior, bukan implementation
// ✅ Use React Testing Library
// ✅ Test user interactions
// ✅ Mock external dependencies
// ✅ Aim untuk high coverage// ❌ Wrong - mutating state
state.name = 'John';
setState(state);
// ✅ Correct - creating new object
setState({ ...state, name: 'John' });// ❌ Wrong - missing dependency
useEffect(() => {
console.log(count);
}, []);
// ✅ Correct - include semua dependencies
useEffect(() => {
console.log(count);
}, [count]);// ❌ Wrong - using index as key
{items.map((item, index) => <li key={index}>{item}</li>)}
// ✅ Correct - using unique identifier
{items.map((item) => <li key={item.id}>{item}</li>)}// ❌ Wrong - creates new function pada setiap render
<button onClick={() => handleClick()}>Click</button>
// ✅ Correct - use useCallback
const handleClick = useCallback(() => {
// handle click
}, []);
<button onClick={handleClick}>Click</button>// ❌ Wrong - memory leak
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
}, []);
// ✅ Correct - cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(timer);
}, []);React telah revolutionize frontend development dengan introducing declarative, component-based approach untuk building user interfaces. Understanding core concepts-nya—components, JSX, props, state, dan hooks—adalah fundamental untuk becoming proficient React developer.
Task management application yang kita build demonstrate semua concepts ini dalam action. Dengan mastering fundamentals ini dan following best practices, Anda akan bisa build scalable, maintainable React applications.
Key takeaways:
Next steps:
React adalah journey, bukan destination. Keep learning, building, dan improving skills Anda.