So far every component has been static — it renders the same thing every time. Two ideas turn a component into something genuinely interactive: props, which let a parent pass data down into a child, and state, which lets a component remember and update its own data.
Props: data flowing down
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
function App() {
return <Greeting name="Ada" />;
}Props work like function arguments — App passes name="Ada" down, and Greeting receives it as a plain object it can destructure. Props are read-only from the child's point of view; a component should never try to change a prop it was given. If a value needs to change, it belongs in state instead, usually in whichever component actually owns that data.
State: a component's own memory
The useState hook is how a function component keeps a value between renders and re-renders itself when that value changes.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}useState(0) gives you back two things: the current value (count) and a function to update it (setCount). Calling setCount does two things — it stores the new value, and it tells React "re-render this component," which is exactly why the number on screen updates the moment you click.
A gotcha: state updates are not instant
function handleClick() {
setCount(count + 1);
console.log(count); // still the OLD value here
}State updates are scheduled, not applied immediately in the middle of your function. If you need to update state based on its previous value reliably, especially inside loops or fast successive calls, pass a function instead: setCount(prev => prev + 1). That guarantees you are always working from the latest value, not a stale one captured earlier.
Props down, events up
A very common pattern is a parent owning the state and passing both the value and an updater function down as props, so a child can trigger a change without owning the data itself:
function App() {
const [count, setCount] = useState(0);
return <Counter count={count} onIncrement={() => setCount(count + 1)} />;
}
function Counter({ count, onIncrement }) {
return <button onClick={onIncrement}>Count: {count}</button>;
}This "lifting state up" pattern is one of the first real architectural decisions you will make in a React app, and you will use it constantly once components need to share data with their siblings.