Skip to main content

Command Palette

Search for a command to run...

Props + State: The Secret Sauce Behind Interactive React

Updated
6 min readView as Markdown
Props + State: The Secret Sauce Behind Interactive React

Introduction

If you’re learning React, mastering state and props is essential. These two concepts power React’s data flow and make components interactive — props let components receive configurable data from their parents, while state lets a component manage and update its own data over time.

In this post we’ll cover:

  • What props are and how to pass data between components

  • What state is, how to use useState, and when to choose state over props

  • How state and props work together to build responsive, interactive UIs

By the end, you’ll understand when to use props vs state and be able to build React components that manage data and respond to user actions.


What Are Props?

Props (short for “properties”) are read‑only pieces of data passed from a parent component to a child component. They allow components to be reusable and configurable.

Passing Props

function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

function App() {
  return <Greeting name="Alice" />;
}

Here the Greeting component receives a name prop and displays it. Props can be any JavaScript value – strings, numbers, arrays, objects, even functions.

Destructuring Props

A cleaner way to access props is to destructure them in the function parameter:

function Greeting({ name }) {
  return <h1>Hello, {name}!</h1>;
}

Props Are Immutable

A child component must never modify its props. Props are read‑only. If you need to change data, that’s where state comes in.


What Is State?

State is data that a component manages internally. Unlike props, state can be changed over time (e.g., in response to user input, network responses, etc.).

In functional components, we use the useState hook to add state.

Using useState

import { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • useState returns an array with two items: the current state value and a function to update it.

  • When setCount is called, React re‑renders the component with the new state.

Rules of Hooks

  • Only call hooks at the top level of a functional component (not inside loops, conditions, or nested functions).

  • Only call hooks from React functions (components or custom hooks).


State vs Props – A Side‑by‑Side Comparison

Feature Props State
Mutability Immutable Mutable (via setter)
Who controls it? Parent component Component itself
Where is it defined? Passed from parent Declared inside the component
Purpose Configure component Manage dynamic data
Use when You need to pass data down You need to change data over time

Lifting State Up

Sometimes two sibling components need to share the same data. The React way is to lift the state up to their closest common ancestor.

function Parent() {
  const [sharedValue, setSharedValue] = useState('');

  return (
    <div>
      <ChildA value={sharedValue} onChange={setSharedValue} />
      <ChildB value={sharedValue} />
    </div>
  );
}

function ChildA({ value, onChange }) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

function ChildB({ value }) {
  return <p>Current value: {value}</p>;
}

Now both children react to the same state, and changes in ChildA automatically update ChildB.


Practical Example – A Simple Todo List

Let’s combine state and props to build a working todo list.

import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

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

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

  return (
    <div>
      <h1>My Todo List</h1>
      <div>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Add a task"
        />
        <button onClick={addTodo}>Add</button>
      </div>
      <TodoList todos={todos} onToggle={toggleTodo} />
    </div>
  );
}

function TodoList({ todos, onToggle }) {
  return (
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          onClick={() => onToggle(todo.id)}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
}
  • TodoApp manages the state for todos and the input.

  • It passes todos and a handler (onToggle) down to TodoList as props.

  • TodoList renders the list and calls onToggle when an item is clicked.


Conclusion

Props and state are the two fundamentals that make React apps interactive and maintainable. Remember these core ideas:

  • Props: read‑only inputs a parent passes to a child — use them to configure components and send data or callbacks downward.

  • State: local, mutable data a component manages with hooks like useState — use it for UI that changes over time (form values, toggles, fetched data).

  • Rule of thumb: if multiple components need the same data, lift state up to the closest common ancestor and pass it down via props (or use Context for broader sharing).

  • Never mutate props directly; instead, update state in the owner and pass new values down.

  • Pass functions as props to let children request state changes in their parents (events → callbacks → state updates).

  • Minimize unnecessary state: derive values from props/state instead of duplicating them.

  • Address prop‑drilling with Context or state managers when many intermediate components only forward props.

  • Keep performance in mind: memoize pure child components with React.memo and use hooks like useCallback and useMemo when appropriate.

Next steps: try a small exercise — build a parent component that manages a list (state), renders child item components (props), and lets children remove or update items via callback props. It’ll reinforce how props and state work together in real apps.

Bonus Challenge: Extend the todo list with a delete button for each item. Use the same pattern – pass a delete handler down via props.