Key Takeaways
- Props are component inputs. A component reads them; its parent can supply different values on later renders.
- State is a component’s memory between renders. Request changes through its setter rather than mutating the current value.
- Pass state values and callback functions to children through props. The callback lets a child request an update without taking ownership of the state.
- Keep state close to where it’s used. Lift shared state to the nearest common parent, not automatically to the application root.
The difference between React props vs state is ownership: props bring information into a component, while state holds information that the component owns and updates over time. A value can be state in a parent and a prop in its child—the same data, viewed from different components.
The JSX examples below are independent replacements for src/App.jsx in an existing client-side React project using the modern JSX transform. Need a starting project? Follow the React environment setup tutorial; review React function components if the component syntax is unfamiliar.
What Are React Props?
Props, short for properties, carry inputs from a parent component into a child. Think of them like function arguments: the parent supplies information, and the child uses it to produce its output.
function Header({ firstName }) {
return <p>Welcome back, {firstName}</p>;
}
export default function App() {
return <Header firstName="Eric" />;
}App passes the string "Eric" through the firstName prop. The parameter { firstName } uses JavaScript destructuring to read that property from the props object.
Props aren’t limited to strings or static configuration. They can contain numbers, booleans, objects, arrays, and functions. Nested JSX is passed through the children prop, which lets a component wrap content supplied by its parent.
What Is React State?
State is information React remembers for a component between renders. In a function component, useState gives you a current value and a setter function for requesting updates. Changing an ordinary local variable doesn’t tell React to render again.
import { useState } from 'react';
function Header({ firstName }) {
return <p>Welcome back, {firstName}</p>;
}
export default function App() {
const [firstName, setFirstName] = useState('Eric');
function handleRename() {
setFirstName('Fred');
}
return (
<div>
<Header firstName={firstName} />
<button type="button" onClick={handleRename}>
Update name
</button>
</div>
);
}Here, firstName starts as 'Eric'. Clicking the button calls setFirstName('Fred'), requesting an update. When React processes it, App renders with the new state and passes 'Fred' to Header.
firstName is state inside App, but a prop inside Header. Header doesn’t need its own state to display the updated greeting. Call useState at the top level of a function component or custom Hook, not inside a condition, loop, or event handler.
React Props vs State: The Important Differences
| Question | Props | Local component state |
|---|---|---|
| What is its role? | Inputs supplied to a component | Information a component remembers between renders |
| Who controls it? | The component supplying the props | The component declaring the state |
| Can it change over time? | Yes, when the parent supplies different values | Yes, through state updates |
| Should the receiving component mutate it? | No | No; use the setter |
| How does a child receive it? | Through its props | The owner can pass its state value as a prop |
Read-Only Does Not Mean Static
A child should not assign to its props or mutate an object received through props. That’s an ownership rule, not a claim that the values can never change. The parent can pass a different name, object, or callback on a later render.
State values are also read-only snapshots for a particular render. Don’t describe state as directly mutable: use its setter to request the next value instead.
State Updates Use Snapshots and May Be Batched
A snapshot is the state value associated with one render. Calling a setter doesn’t change the variable in the event handler that’s already running. React also batches updates—grouping update work—so don’t assume each setter call produces an immediate screen update.
When the next value depends on the previous value, use a functional updater:
import { useState } from 'react';
export default function App() {
const [count, setCount] = useState(0);
function handleAddThree() {
setCount(previousCount => previousCount + 1);
setCount(previousCount => previousCount + 1);
setCount(previousCount => previousCount + 1);
console.log(count); // Still this render's value.
}
return (
<div>
<p>Count: {count}</p>
<button type="button" onClick={handleAddThree}>
Add 3
</button>
</div>
);
}The first click logs 0 and updates the displayed count to 3 when React processes the updates. Each queued updater receives the result of the previous one.
Calling setCount(count + 1) three times in that same handler would instead request the same replacement value three times, adding only 1. Updater functions must be pure: calculate and return the next value without changing existing state or performing side effects.
Update Objects and Arrays Without Mutation
State can hold objects and arrays, but you should replace them rather than change them in place. Here’s a complete object-state example:
import { useState } from 'react';
export default function App() {
const [user, setUser] = useState({
firstName: 'Eric',
lastName: 'Smith'
});
function handleRename() {
setUser(previousUser => ({
...previousUser,
firstName: 'Fred'
}));
}
return (
<div>
<p>Welcome back, {user.firstName} {user.lastName}</p>
<button type="button" onClick={handleRename}>
Update name
</button>
</div>
);
}The spread expression copies the existing properties into a new object, then replaces firstName, preserving lastName. Spread is shallow: for a nested update, copy every object along the path you change.
For arrays, use operations such as map, filter, or array spread to build a replacement array. Replace changed objects inside the array too; copying the array doesn’t copy its objects. See React’s guide to updating arrays in state for worked examples.
Type Checking: TypeScript and Runtime Validation
TypeScript can check a component’s expected props before your application runs. In a TypeScript-enabled React project, this complete example belongs in src/App.tsx:
type HeaderProps = {
firstName: string;
};
function Header({ firstName }: HeaderProps) {
return <p>Welcome back, {firstName}</p>;
}
export default function App() {
return <Header firstName="Eric" />;
}HeaderProps requires a string firstName. A type checker can flag a missing prop or a number passed in its place. TypeScript files containing JSX use the .tsx extension.
TypeScript doesn’t validate external runtime data by itself because its type annotations are erased. Whether you use JavaScript or TypeScript, validate untrusted API responses and other external inputs at the boundary where they enter your application.
Callback Props: Let a Child Request a Parent Update
A callback prop is a function passed to a child for the child to invoke later. It lets the child report an event while the parent decides how to update its state. This is still one-way data flow, not two-way binding.
In this example, Header contains the button, while its sibling NameSummary displays the same name. Their nearest common parent, ProfilePanel, owns the shared state:
import { useState } from 'react';
function Header({ firstName, onRename }) {
return (
<div>
<p>Welcome back, {firstName}</p>
<button type="button" onClick={onRename}>
Update name
</button>
</div>
);
}
function NameSummary({ firstName }) {
return <p>Selected name: {firstName}</p>;
}
export default function ProfilePanel() {
const [firstName, setFirstName] = useState('Eric');
function handleRename() {
setFirstName('Fred');
}
return (
<section>
<Header firstName={firstName} onRename={handleRename} />
<NameSummary firstName={firstName} />
</section>
);
}ProfilePanel owns firstName
|-- firstName + onRename --> Header
|-- firstName ------------> NameSummary
Header button click
--> calls onRename
--> parent's handleRename requests a state update
--> parent renders and passes the new name downHeader never assigns to firstName. It calls the supplied function; ProfilePanel requests the update and supplies the resulting value to both children. Keeping one shared value avoids coordinating two independent copies of the name.
When to Use Props vs State—and Where State Belongs
Props and state aren’t competing storage options. First decide who owns a piece of information; then decide which components need it.
- Keep it local when one component needs it. A menu’s open/closed state can stay in the menu if nothing else needs to control it.
- Lift it when components must coordinate. Move the value to their nearest common parent, then pass the value and event callbacks down. That parent may be deep inside your application.
- Calculate what you can. If a full name follows directly from first and last names, calculate it during rendering rather than storing another synchronized state value.
This ownership approach follows React’s guidance on sharing state between components and avoiding redundant state. Practice the same value-and-callback pattern in the React form example with useState.
Passing props through intermediate components that don’t use them is often called prop drilling. Try clearer component composition first; if distant components need the same information, consider React context. A state-management library may also fit a broader application need, but neither is required for the examples here.
Common Mistakes
- Copying props into state without a reason. Initializing state from a prop doesn’t keep it synchronized with later prop changes. Read the prop directly unless you intentionally want an independent initial value.
- Calling a handler while rendering. Pass
onClick={handleRename}, notonClick={handleRename()}, when you want the function to run on a click. - Expecting every setter call to change the screen. React can skip rendering work when the next state is identical to the current state. Clicking the name button again after it already says Fred doesn’t require a visible change.
FAQ
What is the difference between props and state in React?
Props are inputs supplied to a component; state is information owned and updated by a component. A parent can pass its state to a child as a prop, so the same value can play both roles in different components.
Can a React component change its props?
A component should not assign to its props or mutate objects received through them. Its parent can supply different values on later renders, so read-only props are not necessarily static.
How can a child component update state owned by its parent?
The parent passes a callback prop, and the child invokes it to request a change. The parent remains the owner and passes the updated value down; when siblings need that value, keep it in their nearest common parent.
Are React state updates synchronous?
Don’t assume a setter synchronously changes the current variable or updates the screen. It requests an update, React may batch the work, and the running handler keeps the state snapshot from its render.
Should I always use hooks for state?
No. useState is the usual starting point for local state in a function component, but class components remain supported, and libraries or external stores can own state when the application calls for them.
Further Reading
- Passing Props to a Component — inputs, children, and changing props.
- useState reference — setters, updater functions, and update caveats.
- Queueing a Series of State Updates — batching and functional updaters.
- Sharing State Between Components — lifting state and choosing an owner.
- Updating Objects in State — immutable updates and nested objects.
- Using TypeScript with React — typed props and TSX examples.
- React 19 Upgrade Guide — removal of automatic PropTypes checking.
- TypeScript Handbook: The Basics — static checking and erased type annotations.
