LLevelUp
0
← Back to topic

Props & State

Props and state are the two sources of data in a React component. Understanding both — and understanding why they are fundamentally different — is the foundation for every React pattern that follows.

Props: Inputs to a Component

Props (short for “properties”) are the arguments you pass to a component when you render it. They flow from parent to child, and a component must never modify its own props.

type ButtonProps = {
  label: string;
  variant?: "primary" | "secondary";
  onClick: () => void;
};

function Button({ label, variant = "primary", onClick }: ButtonProps) {
  return (
    <button className={`btn btn--${variant}`} onClick={onClick}>
      {label}
    </button>
  );
}

// Usage
<Button label="Save" onClick={handleSave} />
<Button label="Cancel" variant="secondary" onClick={handleCancel} />

Destructuring props directly in the function signature ({ label, variant = "primary", onClick }) is idiomatic React. The = "primary" syntax provides a default value for the optional variant prop.

IMPORTANT

Props are read-only. A component that mutates its props breaks the contract React relies on to track changes efficiently. If you need to transform a prop, compute a new value — don’t mutate the prop itself.

Passing Any Type as a Prop

Props are not limited to strings and numbers. You can pass functions, objects, arrays, other React elements, or even components.

type CardProps = {
  title: string;
  children: React.ReactNode; // anything React can render
  renderFooter?: () => React.ReactNode; // render prop pattern
};

function Card({ title, children, renderFooter }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card__body">{children}</div>
      {renderFooter && <footer>{renderFooter()}</footer>}
    </div>
  );
}

The special children prop receives whatever is placed between a component’s opening and closing tags.

State: Memory Inside a Component

State is data that belongs to a component and can change over time. When state changes, React re-renders the component to reflect the new value. Props cannot change during a component’s lifetime (from that component’s perspective); state can.

// Without state — the count variable resets on every render
function BrokenCounter() {
  let count = 0;
  return <button onClick={() => count++}>{count}</button>; // never updates
}

// With state — React tracks the value between renders
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

The broken version increments a local variable, but since React has no way to know it changed, it never re-renders. State hands that responsibility to React.

One-Way Data Flow

graph TD
  Parent["Parent Component\n(owns state)"]
  Child1["Child A\n(receives props)"]
  Child2["Child B\n(receives props)"]
  GrandChild["Grandchild\n(receives props)"]

  Parent -- "props" --> Child1
  Parent -- "props" --> Child2
  Child1 -- "props" --> GrandChild
  GrandChild -- "callback prop (event)" --> Child1
  Child1 -- "calls handler" --> Parent

Data flows down through props. Events and callbacks flow up — a child calls a function passed to it as a prop, which triggers a state update in the parent, which re-renders the tree. This single direction makes data easy to trace and debug.

Props vs State: How to Decide

QuestionAnswer
Does it come from outside the component?Props
Does it change over time (user interaction, timer, fetch)?State
Can you compute it from existing props or state?Neither — derive it
Does every instance of this component need its own copy?State per instance
Is it shared across many components?Lift to a common ancestor

TIP

Before reaching for state, ask: “Can I derive this from something I already have?” Derived values (e.g., const isValid = email.includes("@")) should be plain variables, not state. Redundant state is a source of bugs.

Further Learning

Search these terms to go deeper:

  • “Thinking in React react.dev” — the canonical guide to identifying state, where to put it, and one-way data flow
  • “Kent C. Dodds application state management” — patterns for deciding where state should live
  • “React children prop patterns” — everything you can do with the children prop
  • “TypeScript React props typing guide” — exhaustive patterns for typing component props
  • “Lifting state up react.dev” — the official tutorial on sharing state between siblings