আগের পর্বে আমরা API থেকে ডেটা আনতে শিখেছিলাম। এই পর্বে আমরা একটা মৌলিক React প্যাটার্ন দেখব — Lifting State Up। Props পর্বে শিখেছিলাম চাইল্ড component কখনো নিজের props সরাসরি পাল্টাতে পারে না — তাহলে চাইল্ড থেকে প্যারেন্টে ডেটা "ফেরত পাঠানো" যায় কীভাবে? এই পর্বেই তার উত্তর।
সমস্যাটা কী?
ধরো দুইটা sibling component আছে — একটা সার্চ বক্স, আরেকটা ফলাফলের তালিকা। সার্চ বক্সে যা টাইপ করা হয়, সেটা তালিকা component-এ পৌঁছাতে হবে। কিন্তু দুটো component সরাসরি একে অপরের সাথে কথা বলতে পারে না — তারা শুধু তাদের প্যারেন্টের মাধ্যমে যোগাযোগ করতে পারে।
// ❌ সমস্যা — SearchBox আর ResultList একে অপরের ব্যাপারে কিছু জানে না
function App() {
return (
<div>
<SearchBox /> {/* এখানে টাইপ করা হচ্ছে */}
<ResultList /> {/* এখানে দেখাতে হবে, কিন্তু কীভাবে জানবে? */}
</div>
);
}
সমাধান — State প্যারেন্টে "তুলে আনা"
মূল ধারণা — যে state দুই বা ততোধিক component-এর দরকার, সেটা তাদের সবচেয়ে কাছের কমন প্যারেন্টে রাখতে হয়। তারপর সেটা props হিসেবে নিচে পাঠানো হয়।
function App() {
const [query, setQuery] = React.useState(""); // state প্যারেন্টে থাকছে
return (
<div>
<SearchBox query={query} onQueryChange={setQuery} />
<ResultList query={query} />
</div>
);
}
function SearchBox({ query, onQueryChange }) {
return (
<input value={query} onChange={(e) => onQueryChange(e.target.value)} />
);
}
function ResultList({ query }) {
return <p>তুমি খুঁজছো: {query}</p>;
}
এখন App-ই একমাত্র জায়গা যেখানে query state আছে — দুটো চাইল্ডই সেই একই স্টেট থেকে ডেটা পাচ্ছে (props হিসেবে), আর SearchBox সেটা পাল্টাতে পারছে একটা callback function দিয়ে।
Callback Function — চাইল্ড থেকে প্যারেন্টে "ফেরত পাঠানো"
এখানেই মূল কৌশল — প্যারেন্ট একটা ফাংশন (এখানে onQueryChange, যা আসলে setQuery) prop হিসেবে চাইল্ডে পাঠায়। চাইল্ড সেই ফাংশন কল করলে, আসলে প্যারেন্টের স্টেটই পাল্টে যায় — যদিও চাইল্ড নিজে কখনো সরাসরি স্টেট স্পর্শ করছে না।
| দিক | কী পাঠানো হয় |
|---|---|
| প্যারেন্ট → চাইল্ড | ডেটা (query) — সাধারণ prop |
| চাইল্ড → প্যারেন্ট | একটা ফাংশন কল (onQueryChange(newValue)) |
এভাবেই React-এ ডেটা একমুখী (unidirectional) প্রবাহ বজায় রেখেও দুই দিকে "কথা বলা" সম্ভব হয় — ডেটা নিচে যায় props হিসেবে, ঘটনা/পরিবর্তনের অনুরোধ উপরে যায় ফাংশন কলের মাধ্যমে।
লাইভ ডেমো — দুই Sibling Component-এর যোগাযোগ
নিচে TempSlider আর TempDisplay দুটো সম্পূর্ণ আলাদা component, কিন্তু একটার পরিবর্তন সরাসরি আরেকটায় দেখা যাচ্ছে — কারণ state App-এ "তোলা" আছে।
স্লাইডার আর ডিসপ্লে একে অপরকে চেনে না — App-এর temp state-ই তাদের সংযুক্ত করছে।
তালিকা থেকে একটা আইটেম মোছার প্যাটার্ন
এই একই কৌশল ব্যবহার হয় যখন একটা তালিকার আইটেম (চাইল্ড) থেকে "আমাকে মুছে ফেলো" ইভেন্ট প্যারেন্টে পাঠাতে হয় — অনেকটা Advanced JS পর্ব ১২-এর To-do List-এর মতো, কিন্তু এবার React component দিয়ে।
function TodoApp() {
const [todos, setTodos] = React.useState([
{ id: 1, text: "বাজার করা" },
{ id: 2, text: "কোড রিভিউ" }
]);
function handleDelete(id) {
setTodos(todos.filter((todo) => todo.id !== id)); // Advanced JS পর্ব ৫-এর filter প্যাটার্ন
}
return (
<div>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onDelete={handleDelete} />
))}
</div>
);
}
function TodoItem({ todo, onDelete }) {
return (
<div>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>মুছো</button>
</div>
);
}
এখানে TodoItem নিজে কখনো todos অ্যারে সম্পর্কে জানেই না — এটা শুধু onDelete(todo.id) কল করছে, আর প্যারেন্ট TodoApp-ই সিদ্ধান্ত নিচ্ছে সেই আইডি দিয়ে কী করতে হবে (এখানে filter করা)।
কমন প্যারেন্ট কীভাবে খুঁজে বের করবে
যখন দুইটা component-এর মধ্যে ডেটা শেয়ার করতে হয়, একটা প্রশ্ন করো: "এই দুইটার সবচেয়ে কাছের কমন প্যারেন্ট কে?" — সেই কমন প্যারেন্টেই state রাখতে হবে।
App ├── Header ├── FilterPanel <- এখানে ফিল্টার সিলেক্ট হয় └── ProductGrid <- এখানে ফিল্টার করা প্রোডাক্ট দেখাতে হয় // FilterPanel আর ProductGrid-এর কমন প্যারেন্ট হলো App // তাই filter state App-এই থাকা উচিত
একটা সম্পূর্ণ উদাহরণ — ফিল্টার + তালিকা
চলো একটা বাস্তব উদাহরণ দেখি — একটা ফিল্টার component আর একটা তালিকা component, দুটো সম্পূর্ণ আলাদা, প্যারেন্টের মাধ্যমে সংযুক্ত।
function BookLibrary() {
const [filter, setFilter] = React.useState("all"); // "all", "available", "borrowed"
const books = [
{ id: 1, title: "সোনার তরী", status: "available" },
{ id: 2, title: "পথের পাঁচালী", status: "borrowed" },
{ id: 3, title: "চোখের বালি", status: "available" }
];
const filteredBooks =
filter === "all" ? books : books.filter((book) => book.status === filter);
return (
<div>
<FilterButtons currentFilter={filter} onFilterChange={setFilter} />
<BookGrid books={filteredBooks} />
</div>
);
}
function FilterButtons({ currentFilter, onFilterChange }) {
return (
<div>
<button onClick={() => onFilterChange("all")}>সব</button>
<button onClick={() => onFilterChange("available")}>পাওয়া যাচ্ছে</button>
<button onClick={() => onFilterChange("borrowed")}>ধার নেওয়া</button>
</div>
);
}
function BookGrid({ books }) {
return (
<div>
{books.map((book) => <div key={book.id}>{book.title}</div>)}
</div>
);
}
BookLibrary-ই একমাত্র জায়গা যেখানে filter স্টেট আছে, আর সেই একই স্টেট থেকে ফিল্টার করা তালিকাও হিসাব করা হচ্ছে। FilterButtons আর BookGrid সম্পূর্ণ স্বতন্ত্র, একে অপরের অস্তিত্ব সম্পর্কে জানে না।
নিজে চেষ্টা করো
Exercise:
- উপরের
TempSlider/TempDisplayনিজে বানাও, temp-এর ইউনিট (°C/°F) টগল করার একটা তৃতীয় component যোগ করো। - উপরের
BookLibraryনিজে বানাও, একটা নতুন ফিল্টার ("রিজার্ভড") যোগ করো। - উপরের
TodoApp/TodoItemনিজে বানাও, একটা "সম্পন্ন করো" ফাংশনও যোগ করো একই প্যাটার্নে। - ভেবে দেখো — যদি একটা কমেন্ট বক্স আর একটা কমেন্ট কাউন্টার আলাদা component হয়, তাদের কমন প্যারেন্ট কোথায় হওয়া উচিত?
পরের পর্বে যা থাকছে
এতে শেষ হলো "Component যোগাযোগ ও Lifecycle" অংশ। পরের পর্ব থেকে শুরু হবে "Routing ও Styling" — প্রথম টপিক React Router বেসিক, যেখানে আমরা শিখব কীভাবে একটা React অ্যাপে একাধিক পেজ বানানো যায়।