Components, Props, State, Effects
This guide teaches the core React concepts you’ll use constantly in Next.js and React apps:
- Components
- Props
- State
- Effects (
useEffect)
Examples use modern function components and hooks.
1) What is React?
React is a library for building user interfaces by composing small reusable components.
Think of a React app as:
- Data (state/props) + UI (JSX) + Events (handlers)
- When data changes, React re-renders the UI.
2) Components (the building blocks)
A component is a JavaScript/TypeScript function that returns JSX.
2.1 A simple component
export default function Hello() {
return <h1>Hello React</h1>;
}2.2 Using a component
import Hello from "./Hello";
export default function Page() {
return (
<div>
<Hello />
</div>
);
}2.3 JSX basics
JSX looks like HTML but it’s actually JavaScript expressions:
const name = "Alan";
return <p>Hello, {name}</p>;Rules:
- You can embed expressions with
{ ... } - You must return one parent element (wrap with
<div>or<>...</>)
3) Props (inputs to a component)
Props are values passed from a parent component to a child component.
3.1 Define props
type GreetingProps = {
name: string;
};
export function Greeting({ name }: GreetingProps) {
return <h2>Hello, {name}!</h2>;
}3.2 Pass props from parent
import { Greeting } from "./Greeting";
export default function Page() {
return <Greeting name="Alan" />;
}Key ideas:
- Props are read-only (don’t mutate props)
- Props make components reusable
4) State (data that changes over time)
State is internal data a component owns and can update.
Use useState:
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}4.1 Common state patterns
A) Text input state
"use client";
import { useState } from "react";
export default function NameForm() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Type your name"
/>
<p>You typed: {name}</p>
</div>
);
}B) Boolean state (loading)
const [loading, setLoading] = useState(false);
<button disabled={loading} onClick={async () => {
setLoading(true);
// do async work...
setLoading(false);
}}>
{loading ? "Loading..." : "Run"}
</button>4.2 State update tips
- Updating state triggers a re-render
- Never mutate state directly (avoid
state.push(...)on arrays) - Use functional updates when needed:
setCount((prev) => prev + 1);5) Effects (useEffect): reacting to changes or running side effects
An effect is code that runs after render to do “side effects” like:
- fetching data
- subscribing to events
- timers
- reading browser APIs
5.1 Basic useEffect (run once on mount)
"use client";
import { useEffect } from "react";
export default function Page() {
useEffect(() => {
console.log("Runs once after first render");
}, []);
return <div>Check console</div>;
}[] means: run once when component mounts.
5.2 Fetch data with useEffect
"use client";
import { useEffect, useState } from "react";
type Health = { status: string };
export default function HealthCheck() {
const [data, setData] = useState<Health | null>(null);
const [error, setError] = useState("");
useEffect(() => {
const run = async () => {
try {
const res = await fetch("http://localhost:8000/health");
if (!res.ok) throw new Error("Request failed");
setData(await res.json());
} catch (e: any) {
setError(e?.message ?? "Unknown error");
}
};
run();
}, []);
return (
<div>
<h3>Backend health</h3>
{error && <p style={{ color: "crimson" }}>{error}</p>}
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : <p>Loading...</p>}
</div>
);
}5.3 Dependency array: when effects run
useEffect(() => {
// runs when `query` changes
}, [query]);Rules:
[]→ run once[x]→ run whenxchanges- no array → run after every render (usually not what you want)
5.4 Cleanup (important)
Use cleanup for subscriptions/timers:
useEffect(() => {
const id = setInterval(() => console.log("tick"), 1000);
return () => {
clearInterval(id); // cleanup on unmount
};
}, []);6) Putting it all together: a small example
A component that:
- takes props (title)
- uses state (count)
- runs an effect when count changes
"use client";
import { useEffect, useState } from "react";
type Props = { title: string };
export default function CounterCard({ title }: Props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `${title}: ${count}`;
}, [title, count]);
return (
<div style={{ border: "1px solid #ddd", padding: 12, borderRadius: 8 }}>
<h2>{title}</h2>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<span style={{ marginLeft: 8 }}>{count}</span>
</div>
);
}7) Quick checklist (memorize this)
- Component: a function that returns JSX
- Props: inputs passed from parent → child (read-only)
- State: internal data that can change (
useState) - Effect: side effects after render (
useEffect) - Re-render happens when state/props change
- In Next.js App Router, interactive components need
"use client"
8) Common beginner mistakes
- Forgetting
"use client"in Next.js when using hooks/events - Mutating state directly (arrays/objects)
- Missing
[]or wrong dependency array inuseEffect - Calling async directly in useEffect (should wrap in function)
Bad:
useEffect(async () => { ... }, []);Good:
useEffect(() => {
const run = async () => { ... };
run();
}, []);9) Practice tasks
- Build a
Greetingcomponent that takesnameandageas props. - Build a
Counterwith + and - buttons, and disable when count < 0. - Build a
HealthCheckcomponent that calls/healthon mount and shows loading/error states.
If you want, I can also provide a short cheat sheet version (one page) you can keep next to you while coding.