Chapter 11 of 20 · React for Beginners — Complete Free Tutorial
Sharing State by Lifting It Up
You have built components that own their own state in the React for Beginners series. Now comes the scenario every real application hits: two sibling components need to reflect the same changing value. The answer is a principle called lifting state up — move the shared state to their closest common ancestor and pass it down, along with the functions that update it.
The problem: siblings that disagree
Imagine a product page with a search box and a results list. Each component starts with its own state, and nothing synchronises them:
function SearchBox() {
const [query, setQuery] = useState(''); // state lives here…
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
function ResultsList() {
const [query, setQuery] = useState(''); // …and a separate copy here
// never sees SearchBox's value ❌
}
Expected output: typing "vite" filters nothing — the list component has no idea the other state exists. Duplicated state always drifts out of sync.
Lift it to the common parent
Move the single source of truth up to the component that renders both. The parent owns the state; children receive what they need through props:
function ProductPage() {
const [query, setQuery] = useState('');
const visible = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase()),
);
return (
<section>
<SearchBox value={query} onChange={setQuery} />
<ResultsList items={visible} />
</section>
);
}
Expected output: typing "vite" in the box immediately narrows the list to matching products. One state, one owner, two consumers — both stay consistent because they read the same value.
Props down, callbacks up
The pattern has a rhythm worth memorising. Data flows down from parent to child through props; updates flow up through callback functions the parent passes down:
| Direction | Carries | Example |
|---|---|---|
| Down (parent → child) | Values: state, derived data | <SearchBox value={query} /> |
| Up (child → parent) | Callbacks: setters, handlers | onChange={setQuery} |
| Siblings | Nothing directly | They never talk — they share the parent |
A child stays "dumb": it renders what it is given and reports what happened. The parent decides what it means. That separation is what makes components reusable.
State should live where it is used
React's documentation offers a reliable test for choosing the right owner. Ask two questions about every piece of state:
- Is it used by only one component? Then keep it local — right there in that component.
- Is it used by several components, or will it be? Then lift it to their closest shared ancestor.
- Can it be computed from existing state or props? Then do not store it at all — derive it during render (as
visibleis derived fromqueryabove).
Deriving instead of duplicating eliminates the classic bug where a stored copy is forgotten during an update.
A worked example: the temperature converter
The canonical demonstration is two inputs that must agree. Editing either one recalculates the other from shared state in the parent:
function TemperatureConverter() {
const [celsius, setCelsius] = useState('');
const fahrenheit = celsius === '' ? '' : (parseFloat(celsius) * 9) / 5 + 32;
return (
<form>
<input value={celsius} onChange={(e) => setCelsius(e.target.value)} />
<output>{fahrenheit}°F</output>
</form>
);
}
Expected output: entering 100 shows 212°F; entering 37 shows 98.6°F; clearing the input clears the output. There is only one state — fahrenheit is derived, never stored, so it can never disagree.
When lifting gets awkward
Passing state and callbacks through many layers — prop drilling — works fine for a few levels but grows noisy in deep trees. Two honest coping strategies:
- Compose instead of drill — sometimes a component can accept children or callbacks as props and skip a layer entirely (chapter 14).
- Move truly global state to context — themes, current user and locale belong to React context, not an eight-level prop chain.
Neither replaces this chapter's principle: shared state still has one owner; you are only changing how widely it is published.
Common mistakes
- Duplicating state in both children — two copies always drift; keep one in the parent.
- Mutating the child's copy — a child that edits the object it received as a prop breaks the parent's data; call the parent's callback instead.
- Storing derived values — computed-from-state data (counts, filters, totals) belongs in a calculation, not a second
useState. - Passing a whole object when a slice will do — pass just the fields the child needs; unrelated changes then skip needless re-renders.
- Lifting too eagerly — state used by exactly one component has no reason to move up; local state keeps components self-contained.
Practice checklist
- Rebuild the search-and-results pair, starting with duplicated state, then lift it to the parent.
- Add a "results count" component that also reads the shared query — confirm all three stay in sync.
- Write the temperature converter with one state and a derived value; verify all expected outputs.
- Refactor one stored value that could be computed into a plain derivation during render.
- Draw your component tree and mark where the shared state lives now.
Shared state updates work — but some state changes should happen automatically. That is the job of Effects with useEffect.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.