3 · Connect, test and build · 18 MIN
Share state between two components
The nearest shared parent owns state that siblings need.
When one component edits a value and another displays it, putting separate state in both creates disagreement. Move the value to their common parent. Pass the current value down as a prop and pass a callback down for updates. The input calls the callback, the parent updates state, and both children receive the new value. This is called lifting state up. It solves a concrete sharing problem without introducing a global state library.
Locate the single useState call. Follow value from App to TopicInput and topic from App to Preview. Follow an edit in the opposite direction through the callback. Render the second preview using the same topic prop.
Before you start
Install Node.js 22.12+ (or a supported newer LTS). In a terminal run: npm create vite@latest codingneed-react -- --template react. Then cd codingneed-react, npm install, and npm run dev. Open the local URL printed by Vite. Replace src/App.jsx with ONE lesson example at a time. JSX renders a browser interface; it is not a plain console script.
File for this example: App.jsx
New words, explained
- callback
- A function passed to another component or function to be called later.
- sibling
- Components rendered by the same parent.
- sourceOfTruth
- The one place that owns a particular changing value.
Follow the example step by step
- Locate the single useState call.
- Follow value from App to TopicInput and topic from App to Preview.
- Follow an edit in the opposite direction through the callback.
- Render the second preview using the same topic prop.
You are ready to move on when: Both previews display the same topic. Clear displays the placeholder in both. Only App calls useState.
Read the example
import {useState} from 'react';
function TopicInput({value, onChange}) {
return <label>Topic <input value={value} onChange={event => onChange(event.target.value)} /></label>;
}
function Preview({topic}) { return <p>Your next session: {topic || 'Choose a topic'}</p>; }
export default function App() {
const [topic, setTopic] = useState('React');
return <main><h1>Session planner</h1><TopicInput value={topic} onChange={setTopic} />
<Preview topic={topic} /><button onClick={() => setTopic('')}>Clear</button></main>;
}Check the expected output
The input and preview both start with React. Editing the input updates the preview. Clear empties both.
Your challenge
Add a second Preview below the first. Confirm that editing one input updates both previews without duplicating state.
Solution cost: Discuss the operations in this small example; rendering and I/O costs depend on the host. time · Proportional to the example’s retained data. space
Common trap
A prop is read-only. Invoke onChange instead of assigning to value.
Study the project implementation
import {useState} from 'react';
function TopicInput({value, onChange}) {
return <label>Topic <input value={value} onChange={event => onChange(event.target.value)} /></label>;
}
function Preview({topic}) { return <p>Your next session: {topic || 'Choose a topic'}</p>; }
export default function App() {
const [topic, setTopic] = useState('React');
return <main><h1>Session planner</h1><TopicInput value={topic} onChange={setTopic} />
<Preview topic={topic} /><button onClick={() => setTopic('')}>Clear</button></main>;
}Further reading: Official documentation
Next lesson: Use an effect only for an external system →