Components, Props, and State
Components, Props, and State
Every React application is built with three fundamental concepts: components, props, and state. Understanding these three ideas will let you read and work with any React code that AI generates.
Functional Components
In modern React, components are plain JavaScript functions that return JSX:
// The simplest possible component function Hello() { return <h1>Hello, World!</h1>; }
You can also write components as arrow functions — AI tools use both styles:
// Arrow function component (same thing, different syntax) const Hello = () => { return <h1>Hello, World!</h1>; };
Components must follow two rules:
- Start with a capital letter —
UserCard, notuserCard - Return JSX — the HTML-like syntax that defines what to render
Using Components
Once you have a component, you use it like an HTML tag:
function App() { return ( <div> <Hello /> <Hello /> <Hello /> </div> ); }
Each <Hello /> renders an independent copy of the component. This is reusability — write once, use everywhere.
Props: Passing Data to Components
Props (short for "properties") are how you pass data from a parent component to a child component. Think of them as function arguments:
// Define what props the component accepts interface GreetingProps { name: string; message?: string; // Optional prop } // Use props in the component function Greeting({ name, message = "Welcome!" }: GreetingProps) { return ( <div> <h1>Hello, {name}!</h1> <p>{message}</p> </div> ); } // Pass props when using the component function App() { return ( <div> <Greeting name="Alice" /> <Greeting name="Bob" message="Good to see you!" /> </div> ); }
Key points about props:
- Props flow one direction: parent to child (this is called "one-way data flow")
- Props are read-only — a component cannot modify its own props
- Use an interface to define what props a component accepts
- Use
?to make props optional and=to set defaults
What to ask your AI: "Create a Card component that accepts title, description, imageUrl, and an optional onClick handler as props."
Common Prop Types
Here are the prop types you'll see most often:
interface ComponentProps { // Basic types title: string; count: number; isActive: boolean; // Arrays items: string[]; users: User[]; // Functions (callbacks) onClick: () => void; onChange: (value: string) => void; onSubmit: (data: FormData) => Promise<void>; // Children (nested content) children: React.ReactNode; // Optional props className?: string; variant?: "primary" | "secondary" | "danger"; size?: "sm" | "md" | "lg"; }
Passing Different Types of Props
<Button label="Click me" {/* string */} count={42} {/* number */} isDisabled={false} {/* boolean */} items={["a", "b", "c"]} {/* array */} onClick={() => console.log("hi")} {/* function */} style={{ color: "red" }} {/* object */} />
Notice: strings use quotes, everything else uses curly braces { }.
State: Managing Dynamic Data
State is data that changes over time. When state changes, React automatically re-renders the component to show the updated data.
You manage state with the useState hook:
import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); // ↑ ↑ ↑ // value setter function initial value return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}> Increment </button> <button onClick={() => setCount(0)}> Reset </button> </div> ); }
How useState works:
- Call
useState(initialValue)— returns a pair:[currentValue, setterFunction] - Read the current value with the first variable (
count) - Update the value by calling the setter (
setCount(newValue)) - When you call the setter, React re-renders the component with the new value
State with Different Types
function UserForm() { // String state const [name, setName] = useState(""); // Boolean state const [isSubmitting, setIsSubmitting] = useState(false); // Object state (need explicit type when starting with null) const [user, setUser] = useState<User | null>(null); // Array state (need explicit type when starting with empty array) const [items, setItems] = useState<string[]>([]); // Adding to an array (never mutate directly — always create a new array) const addItem = (item: string) => { setItems([...items, item]); // Spread existing + add new }; // Removing from an array const removeItem = (index: number) => { setItems(items.filter((_, i) => i !== index)); }; // Updating object state const updateName = (newName: string) => { setUser(prev => prev ? { ...prev, name: newName } : null); }; }
What to ask your AI: "I need a component with a form that has name, email, and message fields. Set up the state and onChange handlers."
Lifting State Up
When two components need to share the same data, you lift the state up to their nearest common parent:
// ❌ Bad — each component has its own separate count function App() { return ( <div> <CounterDisplay /> {/* Has its own count */} <CounterControls /> {/* Has its own count — not synced! */} </div> ); } // ✅ Good — state lives in the parent, passed down via props function App() { const [count, setCount] = useState(0); return ( <div> <CounterDisplay count={count} /> <CounterControls onIncrement={() => setCount(count + 1)} onReset={() => setCount(0)} /> </div> ); } function CounterDisplay({ count }: { count: number }) { return <p>Count: {count}</p>; } interface CounterControlsProps { onIncrement: () => void; onReset: () => void; } function CounterControls({ onIncrement, onReset }: CounterControlsProps) { return ( <div> <button onClick={onIncrement}>+1</button> <button onClick={onReset}>Reset</button> </div> ); }
This pattern appears everywhere in React. The parent "owns" the state and passes it down. Children communicate back up through callback functions passed as props.
Putting It All Together: A Practical Example
Here's a complete example combining components, props, and state:
interface Todo { id: number; text: string; completed: boolean; } function TodoApp() { const [todos, setTodos] = useState<Todo[]>([]); const [input, setInput] = useState(""); const addTodo = () => { if (!input.trim()) return; const newTodo: Todo = { id: Date.now(), text: input, completed: false, }; setTodos([...todos, newTodo]); setInput(""); }; const toggleTodo = (id: number) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )); }; return ( <div className="max-w-md mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">Todo List</h1> <div className="flex gap-2 mb-4"> <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Add a todo..." className="border rounded px-3 py-2 flex-1" /> <button onClick={addTodo} className="bg-blue-500 text-white px-4 py-2 rounded"> Add </button> </div> <TodoList todos={todos} onToggle={toggleTodo} /> </div> ); } interface TodoListProps { todos: Todo[]; onToggle: (id: number) => void; } function TodoList({ todos, onToggle }: TodoListProps) { if (todos.length === 0) { return <p className="text-gray-500">No todos yet. Add one above!</p>; } return ( <ul className="space-y-2"> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={onToggle} /> ))} </ul> ); } interface TodoItemProps { todo: Todo; onToggle: (id: number) => void; } function TodoItem({ todo, onToggle }: TodoItemProps) { return ( <li onClick={() => onToggle(todo.id)} className={`p-3 border rounded cursor-pointer ${ todo.completed ? "line-through text-gray-400 bg-gray-50" : "bg-white" }`} > {todo.text} </li> ); }
Notice how state flows: TodoApp owns the state, TodoList receives it as props, and TodoItem communicates changes back up through the onToggle callback.
Key Takeaways
| Concept | What It Does | Key Rule |
|---|---|---|
| Component | A function that returns UI | Must start with capital letter |
| Props | Data passed from parent to child | Read-only, one-way flow |
| State | Data that changes over time | Use useState, never mutate directly |
| Lifting state | Moving state to a common parent | Share state via props, communicate via callbacks |
What to ask your AI: "Build a [feature] component. It needs to manage [this data] as state and accept [these inputs] as props."
What's Next?
Components, props, and state are the foundation. The next tutorial covers React Hooks — the powerful functions that let you add effects, refs, and custom logic to your components.