Books/React & Next.js Essentials/Components, Props, and State

    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:

    1. Start with a capital letterUserCard, not userCard
    2. 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:

    1. Call useState(initialValue) — returns a pair: [currentValue, setterFunction]
    2. Read the current value with the first variable (count)
    3. Update the value by calling the setter (setCount(newValue))
    4. 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

    ConceptWhat It DoesKey Rule
    ComponentA function that returns UIMust start with capital letter
    PropsData passed from parent to childRead-only, one-way flow
    StateData that changes over timeUse useState, never mutate directly
    Lifting stateMoving state to a common parentShare 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.


    🌐 www.genai-mentor.ai