Toolmingo
Guides20 min read

React Tutorial for Beginners (2025): Learn React Step by Step

Complete React tutorial for absolute beginners. Learn React components, hooks, state, props, and build real projects. Free guide with code examples.

React is the world's most popular JavaScript library for building user interfaces — used by Facebook, Netflix, Airbnb, and millions of apps worldwide. This tutorial takes you from zero to building real React applications, step by step.

What you'll learn

Topic What you'll be able to do
Setup Create a React app and run it locally
JSX Write HTML-like code inside JavaScript
Components Build reusable UI building blocks
Props Pass data between components
State Make your UI interactive
Hooks Use React's most important built-in hooks
Events Handle clicks, inputs, and form submissions
Lists & Keys Render dynamic data
Conditional rendering Show/hide UI based on state
Projects Build 3 real React apps

React version used: React 19 (latest stable as of 2025)

Prerequisites: Basic JavaScript knowledge (variables, functions, arrays, objects). If you're new to JavaScript, read JavaScript Tutorial for Beginners first.


Part 1 — Why React?

Feature Why it matters
Component-based Build complex UIs from small, reusable pieces
Declarative Describe what the UI should look like — React handles the DOM
Huge ecosystem React Router, Redux, Next.js, thousands of libraries
Job market Most in-demand frontend skill — ~150k+ job listings
Performance Virtual DOM minimizes expensive real DOM updates
Flexible Web, mobile (React Native), desktop, static sites
Meta-maintained Backed by Meta, used at Facebook, Instagram, WhatsApp

React vs other frameworks

React Vue Angular
Type Library Framework Full framework
Learning curve Medium Low High
Syntax JSX Templates TypeScript + decorators
Flexibility High Medium Low (opinionated)
Job market Highest Medium Medium
Best for SPAs, large apps Beginners, small–medium Enterprise

Part 2 — Setup

2.1 Prerequisites

You need Node.js installed. Check:

node --version   # should be v18+
npm --version    # comes with Node

If not installed, download from nodejs.org (choose LTS).

2.2 Create your first React app

The fastest way to start is Vite (faster than Create React App):

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

Open http://localhost:5173 — you'll see the React starter page.

Alternative: Create React App (classic, slower):

npx create-react-app my-react-app
cd my-react-app
npm start

2.3 Project structure

my-react-app/
├── public/          # Static files (index.html)
├── src/
│   ├── App.jsx      # Root component
│   ├── main.jsx     # Entry point (mounts App)
│   └── index.css    # Global styles
├── package.json
└── vite.config.js

2.4 Recommended editor

Use VS Code with these extensions:

  • ES7+ React/Redux/React-Native snippets — shortcuts like rfce → full component
  • Prettier — auto-formatting
  • ESLint — catches errors

Part 3 — JSX

JSX lets you write HTML-like syntax inside JavaScript. It's not HTML — it's syntactic sugar that React compiles to React.createElement() calls.

// JSX
const element = <h1>Hello, World!</h1>;

// What it compiles to (you never write this manually)
const element = React.createElement('h1', null, 'Hello, World!');

JSX rules

1. Return a single root element — wrap multiple elements in a <div> or <> (Fragment):

// Wrong ❌
return (
  <h1>Title</h1>
  <p>Paragraph</p>
);

// Correct ✅ — use Fragment
return (
  <>
    <h1>Title</h1>
    <p>Paragraph</p>
  </>
);

2. Close all tags — even self-closing ones:

<img src="photo.jpg" alt="Photo" />   // ✅
<input type="text" />                  // ✅
<br />                                 // ✅

3. Use className not class (because class is a reserved JS keyword):

<div className="container">...</div>   // ✅
<div class="container">...</div>       // ❌

4. Use {} for JavaScript expressions:

const name = "Alice";
const age = 25;

return (
  <p>Name: {name}, Age: {age}</p>
);
// Renders: Name: Alice, Age: 25

5. Inline styles use objects with camelCase properties:

const style = { backgroundColor: 'blue', fontSize: '16px' };
<div style={style}>Styled!</div>

// Or inline:
<div style={{ color: 'red', fontWeight: 'bold' }}>Red text</div>

JSX cheat sheet:

JSX HTML equivalent
className class
htmlFor for
onClick onclick
onChange onchange
tabIndex tabindex
{expression} N/A — embeds JS
{/* comment */} <!-- comment -->

Part 4 — Components

Components are the building blocks of React apps. A component is a JavaScript function that returns JSX.

4.1 Function components

// The simplest component
function Greeting() {
  return <h1>Hello, World!</h1>;
}

// Use it like an HTML tag
function App() {
  return (
    <div>
      <Greeting />
      <Greeting />
      <Greeting />
    </div>
  );
}

Rules for components:

  • Name must start with capital letter (Greeting, not greeting)
  • Must return JSX (or null to render nothing)
  • Can be in same file or separate files

4.2 Component in its own file

// src/components/Greeting.jsx
function Greeting() {
  return <h1>Hello, World!</h1>;
}

export default Greeting;
// src/App.jsx
import Greeting from './components/Greeting';

function App() {
  return (
    <div>
      <Greeting />
    </div>
  );
}

export default App;

4.3 Arrow function components

Both styles work identically:

// Function declaration
function Button() {
  return <button>Click me</button>;
}

// Arrow function (also common)
const Button = () => {
  return <button>Click me</button>;
};

// Short arrow (implicit return for single expression)
const Button = () => <button>Click me</button>;

Part 5 — Props

Props (short for "properties") let you pass data into components — like function arguments.

5.1 Basic props

// Define a component that accepts props
function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

// Pass props when using the component
function App() {
  return (
    <div>
      <Greeting name="Alice" />
      <Greeting name="Bob" />
      <Greeting name="Charlie" />
    </div>
  );
}
// Renders: Hello, Alice!  Hello, Bob!  Hello, Charlie!

5.2 Multiple props

function UserCard({ name, age, role }) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>Age: {age}</p>
      <p>Role: {role}</p>
    </div>
  );
}

function App() {
  return (
    <UserCard name="Alice" age={28} role="Developer" />
  );
}

Note: strings use "quotes", everything else (numbers, booleans, objects, arrays) uses {curly braces}.

5.3 Default props

function Button({ label = "Click me", color = "blue" }) {
  return (
    <button style={{ backgroundColor: color }}>
      {label}
    </button>
  );
}

// Uses defaults
<Button />

// Overrides defaults
<Button label="Submit" color="green" />

5.4 children prop

children is a special prop that receives everything between opening and closing tags:

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

function App() {
  return (
    <Card>
      <h2>Title</h2>
      <p>Any content goes here.</p>
    </Card>
  );
}

5.5 Props are read-only

Never modify props inside a component. Props flow one way — from parent to child.

// ❌ Wrong — never mutate props
function Bad({ count }) {
  count = count + 1;  // Don't do this
  return <p>{count}</p>;
}

// ✅ Correct — use the value, don't change it
function Good({ count }) {
  return <p>{count}</p>;
}

Part 6 — State and useState

State is data that changes over time and triggers a re-render when it changes.

6.1 useState hook

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);  // initial value = 0

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

How useState works:

const [value, setter] = useState(initialValue);
  • value — current state value
  • setter — function to update state (triggers re-render)
  • initialValue — starting value (only used on first render)

6.2 State with strings

import { useState } from 'react';

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

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <p>Hello, {name || 'stranger'}!</p>
    </div>
  );
}

6.3 State with booleans (toggle)

import { useState } from 'react';

function Toggle() {
  const [isOn, setIsOn] = useState(false);

  return (
    <div>
      <p>Light is {isOn ? 'ON' : 'OFF'}</p>
      <button onClick={() => setIsOn(!isOn)}>
        Toggle
      </button>
    </div>
  );
}

6.4 State with objects

import { useState } from 'react';

function UserForm() {
  const [user, setUser] = useState({ name: '', email: '' });

  const handleChange = (e) => {
    // Spread existing object, update only changed field
    setUser({ ...user, [e.target.name]: e.target.value });
  };

  return (
    <form>
      <input
        name="name"
        value={user.name}
        onChange={handleChange}
        placeholder="Name"
      />
      <input
        name="email"
        value={user.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <p>Name: {user.name}, Email: {user.email}</p>
    </form>
  );
}

6.5 State with arrays

import { useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState(['Buy milk', 'Walk dog']);
  const [input, setInput] = useState('');

  const addTodo = () => {
    if (!input.trim()) return;
    setTodos([...todos, input]);  // create new array — don't push()
    setInput('');
  };

  const removeTodo = (index) => {
    setTodos(todos.filter((_, i) => i !== index));
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="New todo"
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>×</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

State rules:

  • Never mutate state directly — always create a new value
  • setTodos([...todos, newItem]) ✅ — creates new array
  • todos.push(newItem) then setTodos(todos) ❌ — mutates, won't re-render

Part 7 — Event Handling

React uses camelCase event names and passes event handler functions:

// ❌ HTML-style (doesn't work in React)
<button onclick="doSomething()">Click</button>

// ✅ React-style
<button onClick={doSomething}>Click</button>
<button onClick={() => doSomething()}>Click</button>

7.1 Common events

function EventExamples() {
  return (
    <div>
      {/* Click */}
      <button onClick={() => console.log('clicked')}>Click</button>

      {/* Input change */}
      <input onChange={(e) => console.log(e.target.value)} />

      {/* Form submit */}
      <form onSubmit={(e) => {
        e.preventDefault();  // Prevent page reload
        console.log('submitted');
      }}>
        <button type="submit">Submit</button>
      </form>

      {/* Mouse events */}
      <div
        onMouseEnter={() => console.log('mouse in')}
        onMouseLeave={() => console.log('mouse out')}
      >
        Hover me
      </div>

      {/* Keyboard events */}
      <input
        onKeyDown={(e) => {
          if (e.key === 'Enter') console.log('Enter pressed');
        }}
      />
    </div>
  );
}

7.2 Event object

function InputTracker() {
  const handleChange = (e) => {
    console.log(e.target.value);    // current input value
    console.log(e.target.name);     // input name attribute
    console.log(e.target.type);     // "text", "checkbox", etc.
    console.log(e.target.checked);  // for checkboxes
  };

  return <input name="username" onChange={handleChange} />;
}

Common React events

Event Trigger
onClick Mouse click
onChange Input value changes
onSubmit Form submitted
onKeyDown / onKeyUp Key pressed/released
onFocus / onBlur Element focused/unfocused
onMouseEnter / onMouseLeave Mouse hover
onScroll Element scrolled
onLoad Element loaded

Part 8 — Conditional Rendering

Show or hide UI based on state or props.

8.1 if statement

function UserPanel({ isLoggedIn }) {
  if (!isLoggedIn) {
    return <p>Please log in.</p>;
  }
  return <p>Welcome back!</p>;
}

8.2 Ternary operator (most common)

function Status({ isActive }) {
  return (
    <span className={isActive ? 'green' : 'red'}>
      {isActive ? 'Active' : 'Inactive'}
    </span>
  );
}

8.3 && (short-circuit) for optional content

function Notification({ message }) {
  return (
    <div>
      <h1>Dashboard</h1>
      {message && <p className="alert">{message}</p>}
    </div>
  );
}
// If message is empty string / null / undefined, nothing renders

8.4 Early return for complex conditions

function LoadingState({ loading, error, data }) {
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  if (!data) return <p>No data</p>;

  return <div>{data.map(item => <p key={item.id}>{item.name}</p>)}</div>;
}

Part 9 — Lists and Keys

Render arrays of data with .map().

const fruits = ['Apple', 'Banana', 'Cherry'];

function FruitList() {
  return (
    <ul>
      {fruits.map((fruit, index) => (
        <li key={index}>{fruit}</li>
      ))}
    </ul>
  );
}

9.1 Keys — why they matter

React uses keys to track which items changed, added, or removed. Keys must be unique among siblings.

// ❌ No key — React warns and performance suffers
{items.map(item => <li>{item.name}</li>)}

// ✅ Stable ID from data (best)
{items.map(item => <li key={item.id}>{item.name}</li>)}

// ✅ Index (acceptable if list doesn't reorder)
{items.map((item, index) => <li key={index}>{item.name}</li>)}

9.2 Rendering objects

const users = [
  { id: 1, name: 'Alice', role: 'Admin' },
  { id: 2, name: 'Bob', role: 'User' },
  { id: 3, name: 'Charlie', role: 'User' },
];

function UserTable() {
  return (
    <table>
      <thead>
        <tr><th>Name</th><th>Role</th></tr>
      </thead>
      <tbody>
        {users.map(user => (
          <tr key={user.id}>
            <td>{user.name}</td>
            <td>{user.role}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Part 10 — useEffect Hook

useEffect runs side effects — fetching data, subscribing to events, updating the document title, etc.

import { useState, useEffect } from 'react';

useEffect(() => {
  // side effect code here
  return () => {
    // cleanup (optional)
  };
}, [dependencies]);

10.1 Dependency array controls when effect runs

// Run once on mount (empty array)
useEffect(() => {
  console.log('Component mounted');
}, []);

// Run on every render (no array — usually not what you want)
useEffect(() => {
  console.log('Every render');
});

// Run when 'count' changes
useEffect(() => {
  console.log('Count changed to:', count);
}, [count]);

10.2 Fetching data

import { useState, useEffect } from 'react';

function Posts() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
      .then(res => res.json())
      .then(data => {
        setPosts(data);
        setLoading(false);
      });
  }, []);  // fetch once on mount

  if (loading) return <p>Loading...</p>;

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

10.3 Cleanup

Return a cleanup function to prevent memory leaks:

useEffect(() => {
  const interval = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);

  return () => clearInterval(interval);  // cleanup on unmount
}, []);

Part 11 — Other Essential Hooks

11.1 useRef

Ref holds a mutable value that persists across renders without causing re-renders. Most common use: access DOM elements.

import { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus input</button>
    </div>
  );
}

11.2 useMemo

Memoizes an expensive calculation — only re-runs when dependencies change:

import { useState, useMemo } from 'react';

function ExpensiveList({ items }) {
  const [filter, setFilter] = useState('');

  const filteredItems = useMemo(() => {
    // Only re-calculates when items or filter changes
    return items.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  return (
    <>
      <input value={filter} onChange={e => setFilter(e.target.value)} />
      <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>
    </>
  );
}

11.3 useCallback

Memoizes a function — prevents unnecessary re-renders of child components:

import { useState, useCallback } from 'react';

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

  // Without useCallback, new function created every render → Child re-renders
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);  // never changes — empty dependency array

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment parent</button>
      <Child onClick={handleClick} />
    </>
  );
}

Hooks summary

Hook Purpose When to use
useState Component state Any data that changes
useEffect Side effects Fetch, timers, subscriptions
useRef DOM access / mutable value Focus, animations, non-re-render values
useMemo Memoize computed value Expensive calculations
useCallback Memoize function Prevent child re-renders
useContext Read context Avoid prop drilling
useReducer Complex state logic Multi-step state updates

Part 12 — Forms

12.1 Controlled form (recommended)

React controls the input value via state — the "single source of truth":

import { useState } from 'react';

function LoginForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
  });
  const [error, setError] = useState('');

  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!formData.email || !formData.password) {
      setError('All fields are required');
      return;
    }
    setError('');
    console.log('Submitting:', formData);
    // call your API here
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <div>
        <label>Email</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </div>
      <div>
        <label>Password</label>
        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </div>
      <button type="submit">Log In</button>
    </form>
  );
}

12.2 Checkbox and select

function Preferences() {
  const [agreed, setAgreed] = useState(false);
  const [country, setCountry] = useState('us');

  return (
    <form>
      {/* Checkbox */}
      <label>
        <input
          type="checkbox"
          checked={agreed}
          onChange={(e) => setAgreed(e.target.checked)}
        />
        I agree to the terms
      </label>

      {/* Select */}
      <select value={country} onChange={(e) => setCountry(e.target.value)}>
        <option value="us">United States</option>
        <option value="uk">United Kingdom</option>
        <option value="ca">Canada</option>
      </select>

      <p>Country: {country}, Agreed: {String(agreed)}</p>
    </form>
  );
}

Part 13 — Component Composition Patterns

13.1 Lifting state up

When two siblings need to share state, move it to their common parent:

import { useState } from 'react';

// Parent owns the state
function TemperatureConverter() {
  const [celsius, setCelsius] = useState(0);
  const fahrenheit = (celsius * 9/5) + 32;

  return (
    <div>
      <CelsiusInput value={celsius} onChange={setCelsius} />
      <p>{celsius}°C = {fahrenheit.toFixed(1)}°F</p>
    </div>
  );
}

// Children receive state via props
function CelsiusInput({ value, onChange }) {
  return (
    <input
      type="number"
      value={value}
      onChange={(e) => onChange(Number(e.target.value))}
    />
  );
}

13.2 useContext — avoid prop drilling

When you need to share data deeply (e.g. current user, theme, language):

import { createContext, useContext, useState } from 'react';

// 1. Create context
const ThemeContext = createContext('light');

// 2. Provide context at top level
function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Header />
      <Main />
    </ThemeContext.Provider>
  );
}

// 3. Consume context anywhere in the tree
function Header() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <header style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
        Toggle theme
      </button>
    </header>
  );
}

Part 14 — Project 1: Counter App

A classic beginner project — multiple counters with add/remove:

import { useState } from 'react';

function Counter({ id, onRemove }) {
  const [count, setCount] = useState(0);

  return (
    <div style={{ border: '1px solid #ccc', padding: '16px', margin: '8px' }}>
      <h3>Counter #{id}</h3>
      <p style={{ fontSize: '2rem' }}>{count}</p>
      <button onClick={() => setCount(count - 1)}>−</button>
      <button onClick={() => setCount(0)} style={{ margin: '0 8px' }}>Reset</button>
      <button onClick={() => setCount(count + 1)}>+</button>
      <br />
      <button onClick={onRemove} style={{ color: 'red', marginTop: '8px' }}>
        Remove
      </button>
    </div>
  );
}

function App() {
  const [counters, setCounters] = useState([1]);
  const [nextId, setNextId] = useState(2);

  const addCounter = () => {
    setCounters([...counters, nextId]);
    setNextId(nextId + 1);
  };

  const removeCounter = (id) => {
    setCounters(counters.filter(c => c !== id));
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>Counter App</h1>
      <button onClick={addCounter}>Add Counter</button>
      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
        {counters.map(id => (
          <Counter
            key={id}
            id={id}
            onRemove={() => removeCounter(id)}
          />
        ))}
      </div>
    </div>
  );
}

export default App;

What you practised: useState, arrays in state, lifting state, props, event handling, conditional rendering.


Part 15 — Project 2: Todo App

import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build a project', done: false },
  ]);
  const [input, setInput] = useState('');
  const [filter, setFilter] = useState('all');  // all | active | done

  const addTodo = (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    setTodos([
      ...todos,
      { id: Date.now(), text: input.trim(), done: false }
    ]);
    setInput('');
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.done;
    if (filter === 'done') return todo.done;
    return true;
  });

  const remaining = todos.filter(t => !t.done).length;

  return (
    <div style={{ maxWidth: '500px', margin: '40px auto', fontFamily: 'sans-serif' }}>
      <h1>Todo App</h1>

      <form onSubmit={addTodo} style={{ display: 'flex', gap: '8px' }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="What needs to be done?"
          style={{ flex: 1, padding: '8px' }}
        />
        <button type="submit">Add</button>
      </form>

      <div style={{ margin: '12px 0', display: 'flex', gap: '8px' }}>
        {['all', 'active', 'done'].map(f => (
          <button
            key={f}
            onClick={() => setFilter(f)}
            style={{ fontWeight: filter === f ? 'bold' : 'normal' }}
          >
            {f}
          </button>
        ))}
      </div>

      <ul style={{ listStyle: 'none', padding: 0 }}>
        {filteredTodos.map(todo => (
          <li key={todo.id} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px 0' }}>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{ textDecoration: todo.done ? 'line-through' : 'none', flex: 1 }}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>×</button>
          </li>
        ))}
      </ul>

      <p>{remaining} item{remaining !== 1 ? 's' : ''} left</p>
    </div>
  );
}

export default TodoApp;

What you practised: complex state, array manipulation (map/filter), forms, conditional rendering, filtering.


Part 16 — Project 3: Weather App (with API)

Fetches real weather data from a public API:

import { useState } from 'react';

function WeatherApp() {
  const [city, setCity] = useState('');
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const fetchWeather = async (e) => {
    e.preventDefault();
    if (!city.trim()) return;

    setLoading(true);
    setError('');
    setWeather(null);

    try {
      // Free API — no key needed
      const geoRes = await fetch(
        `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`
      );
      const geoData = await geoRes.json();

      if (!geoData.results?.length) {
        setError('City not found');
        return;
      }

      const { latitude, longitude, name, country } = geoData.results[0];

      const weatherRes = await fetch(
        `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,wind_speed_10m,weathercode`
      );
      const weatherData = await weatherRes.json();

      setWeather({
        name,
        country,
        temp: weatherData.current.temperature_2m,
        wind: weatherData.current.wind_speed_10m,
        code: weatherData.current.weathercode,
      });
    } catch {
      setError('Failed to fetch weather. Try again.');
    } finally {
      setLoading(false);
    }
  };

  const getCondition = (code) => {
    if (code === 0) return '☀️ Clear sky';
    if (code <= 3) return '🌤️ Partly cloudy';
    if (code <= 48) return '🌫️ Foggy';
    if (code <= 67) return '🌧️ Rain';
    if (code <= 77) return '❄️ Snow';
    if (code <= 82) return '🌦️ Showers';
    return '⛈️ Thunderstorm';
  };

  return (
    <div style={{ maxWidth: '400px', margin: '40px auto', fontFamily: 'sans-serif', textAlign: 'center' }}>
      <h1>Weather App</h1>

      <form onSubmit={fetchWeather} style={{ display: 'flex', gap: '8px' }}>
        <input
          value={city}
          onChange={(e) => setCity(e.target.value)}
          placeholder="Enter city name..."
          style={{ flex: 1, padding: '8px' }}
        />
        <button type="submit" disabled={loading}>
          {loading ? '...' : 'Search'}
        </button>
      </form>

      {error && <p style={{ color: 'red', marginTop: '16px' }}>{error}</p>}

      {weather && (
        <div style={{ marginTop: '24px', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
          <h2>{weather.name}, {weather.country}</h2>
          <p style={{ fontSize: '3rem', margin: '8px 0' }}>{weather.temp}°C</p>
          <p>{getCondition(weather.code)}</p>
          <p>Wind: {weather.wind} km/h</p>
        </div>
      )}
    </div>
  );
}

export default WeatherApp;

What you practised: useEffect vs fetch-on-demand, async/await, loading/error states, API integration.


Learning path

Stage Focus Time
Beginner JSX, components, props, useState, events 1–2 weeks
Intermediate useEffect, forms, lists, conditional rendering 2–3 weeks
Advanced basics useContext, custom hooks, data fetching 2–3 weeks
React ecosystem React Router, Zustand/Redux, React Query 3–4 weeks
Full-stack Next.js (SSR/SSG), TypeScript + React 4–6 weeks
Production Testing (Jest + RTL), CI/CD, performance Ongoing

After this tutorial, learn next:

  1. React Router — client-side routing, multiple pages
  2. TypeScript + React — type safety for larger projects
  3. React Query / TanStack Query — server state, caching, data fetching
  4. Next.js — SSR, SSG, file-based routing, full-stack React
  5. Zustand or Redux Toolkit — global state management
  6. React Testing Library — component tests

React vs related terms

Term What it is
React JavaScript library for building UI components
Next.js React framework — adds routing, SSR, SSG, API routes
Vite Build tool — faster alternative to Create React App
JSX JavaScript syntax extension for writing HTML-like code
Virtual DOM React's internal representation — diffs and updates real DOM efficiently
Hook Function that lets you use React state/lifecycle in function components
Component Reusable UI building block — a function that returns JSX
State Data that changes over time and triggers UI updates
Props Data passed from parent to child component
Redux Popular global state management library, often paired with React

Common mistakes

Mistake Fix
Mutating state directly (state.items.push(x)) Create new array: setState([...state.items, x])
Missing key prop in lists Add unique key={item.id} to each mapped element
Calling hooks inside loops/conditions Always call hooks at the top level of a component
Forgetting e.preventDefault() in forms Add it to prevent page reload on submit
Infinite loop in useEffect Check dependencies — setters in deps array cause loops
Stale closure in event handler Use functional updater: setCount(c => c + 1)
useEffect without cleanup Return cleanup function to prevent memory leaks
class instead of className React uses className for HTML class attribute

Frequently asked questions

Do I need to know JavaScript before learning React? Yes — learn JavaScript fundamentals first (variables, functions, arrays, objects, ES6+ features like arrow functions, destructuring, spread). The JavaScript Tutorial for Beginners covers everything you need.

React or Next.js — which should I learn first? React first. Next.js is built on top of React — you need React fundamentals before Next.js makes sense. Learn React for a few weeks, then move to Next.js.

Do I need to learn class components? Not if you're starting today. Function components with hooks are the modern standard (since 2019). You might encounter class components in legacy codebases — understanding them is useful but not a priority.

What is the best state management for React in 2025? For local state: useState. For server state: TanStack Query (React Query). For global client state: Zustand (simple) or Redux Toolkit (complex apps). Don't reach for global state managers too early — useState + lifting state handles most apps.

React or Vue — which is better for beginners? Vue has a gentler learning curve. React is more widely used in industry (~3x more jobs). If your goal is employment, React is the better choice. If you want a faster start, Vue.

How long does it take to learn React? Basic components, props, and state: 1–2 weeks of consistent practice. Enough to build a simple app: 4–6 weeks. Job-ready: 3–6 months (including ecosystem tools, projects, TypeScript).

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools