React - The Industry Standard That Pays the Bills
Here’s the thing about React - it’s not necessarily better than Vue, but it’s everywhere. Learning React isn’t just about the technology; it’s about career opportunities.
I’ve been in hiring meetings where React experience was the deciding factor. Not because React is objectively better, but because team velocity matters when you’re shipping features every sprint. When your team already knows React, bringing in someone who can hit the ground running is invaluable.
Why React Won (And What That Means for Your Career)
React didn’t win because it was perfect - it won because Facebook had the resources to build an entire ecosystem around it. When you learn React, you’re not just learning a library; you’re learning an approach that’s used by Netflix, Airbnb, Instagram, WhatsApp, and thousands of companies worldwide.
Facebook’s Influence and Ecosystem
React benefits from having a massive company behind it:
- Continuous development with full-time engineers
- Extensive tooling like Create React App, React DevTools
- Large community producing tutorials, packages, and solutions
- Enterprise adoption driven by Facebook’s credibility
Job Market Reality
Let’s be honest about the numbers. According to Stack Overflow’s developer survey, React consistently ranks as one of the most wanted and loved frameworks. More importantly for your career:
- React jobs often pay 15-20% more than similar positions
- There are significantly more React positions available
- Remote work opportunities are more common with React experience
The Component-First Philosophy
React popularized thinking about UIs as components - self-contained pieces that manage their own state and rendering. This wasn’t revolutionary (other frameworks did it too), but React made it mainstream and shaped how we build modern web applications.
React Fundamentals Through Familiar Territory
You’ve built this task manager with vanilla JavaScript and Vue. Now let’s see how React handles the same problems, and why its approach became the industry standard.
Setting Up React
We’ll use Create React App to get started quickly:
1npx create-react-app task-manager
2cd task-manager
3npm start
This gives you a complete development environment with hot reloading, testing, and build optimization - no configuration required.
JSX - HTML in JavaScript That Makes Sense
JSX looks weird at first, but it’s just JavaScript that lets you write HTML-like syntax:
1function TaskItem({ task, onToggle, onDelete }) {
2 return (
3 <li className={`task-item ${task.completed ? 'completed' : ''}`}>
4 <input
5 type="checkbox"
6 checked={task.completed}
7 onChange={(e) => onToggle(task.id, e.target.checked)}
8 className="task-checkbox"
9 />
10 <div className="task-content">
11 <div className="task-title">{task.title}</div>
12 {task.description && (
13 <div className="task-description">{task.description}</div>
14 )}
15 <div className="task-meta">
16 <span className={`priority-badge priority-${task.priority}`}>
17 {task.priority}
18 </span>
19 <span>Created {formatDate(task.created_at)}</span>
20 </div>
21 </div>
22 <div className="task-actions">
23 <button onClick={() => onDelete(task.id)} className="delete-btn">
24 Delete
25 </button>
26 </div>
27 </li>
28 );
29}
Notice how similar this looks to our Vue template, but it’s actually JavaScript. You can use JavaScript expressions anywhere inside {}
.
Components as Functions vs Classes
Modern React uses functional components with hooks. This is cleaner and more intuitive than the old class-based approach:
1// Modern functional component (use this)
2function TaskManager() {
3 const [tasks, setTasks] = useState([]);
4 const [loading, setLoading] = useState(false);
5
6 return (
7 <div className="task-manager">
8 {/* Component JSX */}
9 </div>
10 );
11}
12
13// Old class component (don't use this anymore)
14class TaskManager extends React.Component {
15 constructor(props) {
16 super(props);
17 this.state = { tasks: [], loading: false };
18 }
19
20 render() {
21 return <div className="task-manager">{/* ... */}</div>;
22 }
23}
Our Task Manager, React Edition
Let’s build the complete task manager step by step. We’ll organize it into components and use modern React patterns:
API Service (Same as Before)
1// src/services/api.js
2const API_BASE_URL = 'https://api.taskmanager.dev/v1';
3
4class ApiError extends Error {
5 constructor(message, status) {
6 super(message);
7 this.status = status;
8 }
9}
10
11async function apiRequest(endpoint, options = {}) {
12 try {
13 const response = await fetch(`${API_BASE_URL}${endpoint}`, {
14 headers: {
15 'Content-Type': 'application/json',
16 ...options.headers
17 },
18 ...options
19 });
20
21 if (!response.ok) {
22 let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
23
24 try {
25 const errorData = await response.json();
26 errorMessage = errorData.message || errorMessage;
27 } catch {
28 // Use the default message if response isn't JSON
29 }
30
31 throw new ApiError(errorMessage, response.status);
32 }
33
34 return await response.json();
35 } catch (error) {
36 if (error instanceof ApiError) {
37 throw error;
38 }
39
40 // Network or other errors
41 throw new ApiError('Network error - check your connection', 0);
42 }
43}
44
45export const api = {
46 getTasks: (filters = {}) => {
47 const params = new URLSearchParams();
48 Object.entries(filters).forEach(([key, value]) => {
49 if (value != null) params.append(key, value);
50 });
51
52 const query = params.toString() ? `?${params}` : '';
53 return apiRequest(`/tasks${query}`);
54 },
55
56 createTask: (taskData) =>
57 apiRequest('/tasks', {
58 method: 'POST',
59 body: JSON.stringify(taskData)
60 }),
61
62 updateTask: (id, updates) =>
63 apiRequest(`/tasks/${id}`, {
64 method: 'PUT',
65 body: JSON.stringify(updates)
66 }),
67
68 deleteTask: (id) =>
69 apiRequest(`/tasks/${id}`, {
70 method: 'DELETE'
71 })
72};
Task Item Component
1// src/components/TaskItem.jsx
2import React from 'react';
3
4function formatDate(dateString) {
5 return new Date(dateString).toLocaleDateString('en-US', {
6 month: 'short',
7 day: 'numeric',
8 hour: '2-digit',
9 minute: '2-digit'
10 });
11}
12
13function TaskItem({ task, onToggle, onDelete }) {
14 return (
15 <li className={`task-item ${task.completed ? 'completed' : ''}`}>
16 <input
17 type="checkbox"
18 checked={task.completed}
19 onChange={(e) => onToggle(task.id, e.target.checked)}
20 className="task-checkbox"
21 />
22 <div className="task-content">
23 <div className="task-title">{task.title}</div>
24 {task.description && (
25 <div className="task-description">{task.description}</div>
26 )}
27 <div className="task-meta">
28 <span className={`priority-badge priority-${task.priority}`}>
29 {task.priority}
30 </span>
31 <span>Created {formatDate(task.created_at)}</span>
32 </div>
33 </div>
34 <div className="task-actions">
35 <button
36 onClick={() => onDelete(task.id)}
37 className="delete-btn"
38 >
39 Delete
40 </button>
41 </div>
42 </li>
43 );
44}
45
46export default TaskItem;
Add Task Form Component
1// src/components/AddTaskForm.jsx
2import React, { useState } from 'react';
3
4function AddTaskForm({ onAddTask, isLoading }) {
5 const [formData, setFormData] = useState({
6 title: '',
7 description: '',
8 priority: 'medium'
9 });
10
11 const handleSubmit = async (e) => {
12 e.preventDefault();
13
14 if (!formData.title.trim()) {
15 return;
16 }
17
18 try {
19 await onAddTask({
20 title: formData.title.trim(),
21 description: formData.description.trim() || null,
22 priority: formData.priority,
23 completed: false
24 });
25
26 // Clear form on success
27 setFormData({
28 title: '',
29 description: '',
30 priority: 'medium'
31 });
32 } catch (error) {
33 // Error handling is done by parent component
34 }
35 };
36
37 const handleInputChange = (e) => {
38 const { name, value } = e.target;
39 setFormData(prev => ({
40 ...prev,
41 [name]: value
42 }));
43 };
44
45 return (
46 <form onSubmit={handleSubmit} className="add-task-form">
47 <div className="form-group">
48 <input
49 type="text"
50 name="title"
51 value={formData.title}
52 onChange={handleInputChange}
53 placeholder="What needs to be done?"
54 required
55 />
56 <select
57 name="priority"
58 value={formData.priority}
59 onChange={handleInputChange}
60 >
61 <option value="low">Low Priority</option>
62 <option value="medium">Medium Priority</option>
63 <option value="high">High Priority</option>
64 </select>
65 </div>
66 <div className="form-group">
67 <textarea
68 name="description"
69 value={formData.description}
70 onChange={handleInputChange}
71 placeholder="Add a description (optional)"
72 rows="2"
73 />
74 </div>
75 <button type="submit" disabled={isLoading}>
76 {isLoading ? 'Adding...' : 'Add Task'}
77 </button>
78 </form>
79 );
80}
81
82export default AddTaskForm;
Filter Buttons Component
1// src/components/FilterButtons.jsx
2import React from 'react';
3
4const FILTERS = [
5 { key: 'all', label: 'All' },
6 { key: 'pending', label: 'Pending' },
7 { key: 'completed', label: 'Completed' }
8];
9
10function FilterButtons({ currentFilter, onFilterChange }) {
11 return (
12 <div className="filters">
13 {FILTERS.map(filter => (
14 <button
15 key={filter.key}
16 className={`filter-btn ${currentFilter === filter.key ? 'active' : ''}`}
17 onClick={() => onFilterChange(filter.key)}
18 >
19 {filter.label}
20 </button>
21 ))}
22 </div>
23 );
24}
25
26export default FilterButtons;
Task List Component
1// src/components/TaskList.jsx
2import React from 'react';
3import TaskItem from './TaskItem';
4
5function TaskList({ tasks, loading, filter, onToggleTask, onDeleteTask }) {
6 if (loading) {
7 return (
8 <div className="loading">
9 <div className="spinner"></div>
10 <p>Loading tasks...</p>
11 </div>
12 );
13 }
14
15 if (tasks.length === 0) {
16 const message = filter === 'all'
17 ? 'Add your first task above to get started!'
18 : `No ${filter} tasks at the moment.`;
19
20 return (
21 <div className="empty-state">
22 <h3>No tasks found</h3>
23 <p>{message}</p>
24 </div>
25 );
26 }
27
28 return (
29 <ul className="task-list">
30 {tasks.map(task => (
31 <TaskItem
32 key={task.id}
33 task={task}
34 onToggle={onToggleTask}
35 onDelete={onDeleteTask}
36 />
37 ))}
38 </ul>
39 );
40}
41
42export default TaskList;
Error Message Component
1// src/components/ErrorMessage.jsx
2import React, { useEffect } from 'react';
3
4function ErrorMessage({ error, onClear }) {
5 useEffect(() => {
6 if (error) {
7 const timer = setTimeout(() => {
8 onClear();
9 }, 5000);
10
11 return () => clearTimeout(timer);
12 }
13 }, [error, onClear]);
14
15 if (!error) return null;
16
17 return (
18 <div className="error-message">
19 {error}
20 <button
21 onClick={onClear}
22 style={{
23 marginLeft: '10px',
24 background: 'rgba(255,255,255,0.2)',
25 border: 'none',
26 color: 'white',
27 padding: '2px 8px',
28 borderRadius: '3px',
29 cursor: 'pointer'
30 }}
31 >
32 ×
33 </button>
34 </div>
35 );
36}
37
38export default ErrorMessage;
Main App Component with Hooks
This is where React’s modern approach really shines. We use hooks to manage state and side effects:
1// src/App.jsx
2import React, { useState, useEffect, useMemo } from 'react';
3import { api } from './services/api';
4import AddTaskForm from './components/AddTaskForm';
5import FilterButtons from './components/FilterButtons';
6import TaskList from './components/TaskList';
7import ErrorMessage from './components/ErrorMessage';
8import './App.css';
9
10function App() {
11 // State management
12 const [tasks, setTasks] = useState([]);
13 const [currentFilter, setCurrentFilter] = useState('all');
14 const [loading, setLoading] = useState(false);
15 const [error, setError] = useState('');
16
17 // Computed values with useMemo
18 const filteredTasks = useMemo(() => {
19 switch (currentFilter) {
20 case 'completed':
21 return tasks.filter(task => task.completed);
22 case 'pending':
23 return tasks.filter(task => !task.completed);
24 default:
25 return tasks;
26 }
27 }, [tasks, currentFilter]);
28
29 const taskStats = useMemo(() => {
30 const total = tasks.length;
31 const completed = tasks.filter(t => t.completed).length;
32 return { total, completed };
33 }, [tasks]);
34
35 // Load tasks on component mount
36 useEffect(() => {
37 loadTasks();
38 }, []);
39
40 // API functions
41 const loadTasks = async () => {
42 try {
43 setLoading(true);
44 setError('');
45 const response = await api.getTasks();
46 setTasks(response.tasks || response);
47 } catch (err) {
48 setError('Failed to load tasks. Please try again.');
49 } finally {
50 setLoading(false);
51 }
52 };
53
54 const handleAddTask = async (taskData) => {
55 try {
56 setLoading(true);
57 setError('');
58 const newTask = await api.createTask(taskData);
59 setTasks(prev => [newTask, ...prev]);
60 } catch (err) {
61 setError('Failed to create task. Please try again.');
62 throw err; // Re-throw so form knows it failed
63 } finally {
64 setLoading(false);
65 }
66 };
67
68 const handleToggleTask = async (taskId, completed) => {
69 const task = tasks.find(t => t.id === taskId);
70 if (!task) return;
71
72 // Optimistic update
73 const originalCompleted = task.completed;
74 setTasks(prev =>
75 prev.map(t =>
76 t.id === taskId ? { ...t, completed } : t
77 )
78 );
79
80 try {
81 await api.updateTask(taskId, { ...task, completed });
82 } catch (err) {
83 // Revert on failure
84 setTasks(prev =>
85 prev.map(t =>
86 t.id === taskId ? { ...t, completed: originalCompleted } : t
87 )
88 );
89 setError('Failed to update task. Please try again.');
90 }
91 };
92
93 const handleDeleteTask = async (taskId) => {
94 if (!window.confirm('Are you sure you want to delete this task?')) {
95 return;
96 }
97
98 // Optimistic update
99 const originalTasks = tasks;
100 setTasks(prev => prev.filter(t => t.id !== taskId));
101
102 try {
103 await api.deleteTask(taskId);
104 } catch (err) {
105 // Revert on failure
106 setTasks(originalTasks);
107 setError('Failed to delete task. Please try again.');
108 }
109 };
110
111 return (
112 <div className="container">
113 <header>
114 <h1>React Task Manager</h1>
115 <div className="stats">
116 <span>{taskStats.total} tasks</span>
117 <span>{taskStats.completed} completed</span>
118 </div>
119 </header>
120
121 <AddTaskForm
122 onAddTask={handleAddTask}
123 isLoading={loading}
124 />
125
126 <FilterButtons
127 currentFilter={currentFilter}
128 onFilterChange={setCurrentFilter}
129 />
130
131 <ErrorMessage
132 error={error}
133 onClear={() => setError('')}
134 />
135
136 <TaskList
137 tasks={filteredTasks}
138 loading={loading}
139 filter={currentFilter}
140 onToggleTask={handleToggleTask}
141 onDeleteTask={handleDeleteTask}
142 />
143 </div>
144 );
145}
146
147export default App;
React Hooks - The Modern Way
React hooks changed everything about how we write React components. Let’s break down the key hooks we’re using:
useState - Managing Component State
1const [tasks, setTasks] = useState([]);
2
3// Functional update pattern
4setTasks(prevTasks => [...prevTasks, newTask]);
5
6// Direct update
7setTasks(newTaskArray);
useEffect - Side Effects and Lifecycle
1// Run once on mount (like componentDidMount)
2useEffect(() => {
3 loadTasks();
4}, []);
5
6// Run when dependencies change
7useEffect(() => {
8 console.log('Tasks changed:', tasks);
9}, [tasks]);
10
11// Cleanup (like componentWillUnmount)
12useEffect(() => {
13 const timer = setTimeout(() => setError(''), 5000);
14 return () => clearTimeout(timer);
15}, [error]);
useMemo - Performance Optimization
1// Only recalculate when tasks or currentFilter changes
2const filteredTasks = useMemo(() => {
3 switch (currentFilter) {
4 case 'completed':
5 return tasks.filter(task => task.completed);
6 case 'pending':
7 return tasks.filter(task => !task.completed);
8 default:
9 return tasks;
10 }
11}, [tasks, currentFilter]);
Custom Hooks (Advanced Pattern)
You could extract the task management logic into a custom hook:
1// src/hooks/useTasks.js
2import { useState, useEffect, useMemo } from 'react';
3import { api } from '../services/api';
4
5export function useTasks() {
6 const [tasks, setTasks] = useState([]);
7 const [loading, setLoading] = useState(false);
8 const [error, setError] = useState('');
9
10 const loadTasks = async () => {
11 try {
12 setLoading(true);
13 setError('');
14 const response = await api.getTasks();
15 setTasks(response.tasks || response);
16 } catch (err) {
17 setError('Failed to load tasks. Please try again.');
18 } finally {
19 setLoading(false);
20 }
21 };
22
23 useEffect(() => {
24 loadTasks();
25 }, []);
26
27 const addTask = async (taskData) => {
28 try {
29 setLoading(true);
30 setError('');
31 const newTask = await api.createTask(taskData);
32 setTasks(prev => [newTask, ...prev]);
33 } catch (err) {
34 setError('Failed to create task.');
35 throw err;
36 } finally {
37 setLoading(false);
38 }
39 };
40
41 return {
42 tasks,
43 loading,
44 error,
45 setError,
46 addTask,
47 updateTask: (id, updates) => { /* implementation */ },
48 deleteTask: (id) => { /* implementation */ }
49 };
50}
51
52// Use in component
53function App() {
54 const { tasks, loading, error, addTask } = useTasks();
55 // ... rest of component
56}
Props and State - React’s Data Flow
React enforces unidirectional data flow. Data flows down through props, and changes flow up through callback functions:
1// Parent passes data down and callbacks up
2<TaskList
3 tasks={filteredTasks} // Data flows down
4 onToggleTask={handleToggle} // Events flow up
5 onDeleteTask={handleDelete} // Events flow up
6/>
7
8// Child receives props and calls callbacks
9function TaskList({ tasks, onToggleTask, onDeleteTask }) {
10 return (
11 <ul>
12 {tasks.map(task => (
13 <TaskItem
14 key={task.id}
15 task={task} // Data flows down
16 onToggle={onToggleTask} // Callbacks flow down
17 onDelete={onDeleteTask} // Callbacks flow down
18 />
19 ))}
20 </ul>
21 );
22}
Event Handling and Form Patterns
React has specific patterns for handling events and forms:
1// Event handlers receive synthetic events
2const handleInputChange = (e) => {
3 const { name, value } = e.target;
4 setFormData(prev => ({
5 ...prev,
6 [name]: value
7 }));
8};
9
10// Controlled components (recommended)
11<input
12 value={formData.title}
13 onChange={handleInputChange}
14 name="title"
15/>
16
17// Uncontrolled components (use sparingly)
18const inputRef = useRef();
19<input ref={inputRef} defaultValue="initial" />
Performance Considerations
React is fast, but you can make it faster:
Key Props for List Rendering
1// Good - stable, unique keys
2{tasks.map(task => (
3 <TaskItem key={task.id} task={task} />
4))}
5
6// Bad - array index as key (can cause bugs)
7{tasks.map((task, index) => (
8 <TaskItem key={index} task={task} />
9))}
Avoiding Unnecessary Re-renders
1// This creates a new function on every render (bad)
2<button onClick={() => handleDelete(task.id)}>Delete</button>
3
4// Better - use useCallback for expensive operations
5const handleDelete = useCallback((id) => {
6 return () => {
7 if (confirm('Delete task?')) {
8 deleteTask(id);
9 }
10 };
11}, [deleteTask]);
12
13<button onClick={handleDelete(task.id)}>Delete</button>
React.memo for Component Optimization
1// Only re-render if props actually changed
2const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete }) {
3 return (
4 // component JSX
5 );
6});
React Developer Tools - Your Debug Companion
Install the React Developer Tools browser extension. It shows you:
- Component hierarchy and how props flow
- State and hooks for each component in real-time
- Performance profiling to identify slow renders
- Time-travel debugging with component state history
React vs Vue vs Vanilla - The Final Comparison
Now that you’ve built the same app three different ways, here’s the honest comparison:
Development Speed
- Vanilla: Slowest initial development, but you control everything
- Vue: Fastest to learn and be productive
- React: Steeper learning curve, but huge ecosystem support
Performance
- Vanilla: Fastest possible performance (if you optimize correctly)
- Vue: Excellent performance with minimal configuration
- React: Good performance, but requires more optimization awareness
Job Market
- Vanilla: Essential foundation, but limited job opportunities
- Vue: Growing market, especially in smaller/medium companies
- React: Largest job market and highest salaries
Learning Curve
- Vanilla: Steep at first, but builds deep understanding
- Vue: Gentle curve, most intuitive for beginners
- React: Moderate curve, but concepts transfer to other frameworks
Bundle Size
- Vanilla: Smallest possible bundle
- Vue: Smaller bundle size (~34KB)
- React: Larger bundle size (~42KB), but heavily optimized
When to Choose React
Choose React when:
- Career opportunities are your priority
- Team expertise - your team already knows React
- Ecosystem needs - you need specific React libraries
- Large-scale applications - React’s patterns scale well
- Mobile development - React Native provides path to mobile
Industry Reality Check
Here’s what I’ve learned from hiring hundreds of developers:
React developers get hired faster because there are simply more React jobs. Companies choose React not because it’s perfect, but because:
- It’s easy to find React developers
- There’s extensive documentation and community support
- The risk is lower when using a technology backed by Facebook
- Most developers already have some React experience
But - companies are increasingly open to Vue and other frameworks. The key is understanding the fundamentals that transfer between all frameworks.
Beyond the Basics - Next Steps
Once you’re comfortable with React basics, explore:
Routing with React Router
1import { BrowserRouter, Routes, Route } from 'react-router-dom';
2
3function App() {
4 return (
5 <BrowserRouter>
6 <Routes>
7 <Route path="/" element={<TaskList />} />
8 <Route path="/completed" element={<CompletedTasks />} />
9 </Routes>
10 </BrowserRouter>
11 );
12}
State Management with Redux or Zustand
1// Simple state management with Zustand
2import { create } from 'zustand';
3
4const useTaskStore = create((set) => ({
5 tasks: [],
6 addTask: (task) => set(state => ({
7 tasks: [...state.tasks, task]
8 })),
9}));
Professional Development Setup
- Next.js for full-stack React applications
- TypeScript for better development experience
- Testing Library for component testing
- Storybook for component development
The Bottom Line
You’ve now built the same application four different ways. Each approach has its strengths:
- REST APIs provide the foundation that supports any frontend
- Vanilla JavaScript teaches you how everything actually works
- Vue.js gives you productivity and a gentle learning curve
- React provides career opportunities and industry standardization
The most important skill isn’t mastering any one framework - it’s understanding the patterns that work across all of them. HTTP requests, state management, component organization, error handling - these concepts transfer everywhere.
Your journey doesn’t end here. Pick the approach that matches your goals: Vue for rapid development and learning, React for career opportunities, or go deeper with vanilla JavaScript to truly understand the web platform.
But remember - underneath every framework is JavaScript doing exactly what you learned in the first article. Understanding these fundamentals makes you the kind of developer who can adapt to any team, any codebase, any challenge.
Welcome to the club. Now go build something amazing.