كيفية إضافة Database Hook متقن إلى مشاريع React باستخدام Easybase
مقدمة: لماذا تحتاج React إلى Database Hook ذكي؟
تُعد React واحدة من أبرز المكتبات المستخدمة لبناء واجهات تفاعلية تعتمد على الحالة stateful interfaces، وقد أصبحت جزءاً أساسياً من تطبيقات الويب الحديثة. تعتمد عليها منصات كبرى مثل Twitter وFacebook وInstagram وAirbnb لتقديم تجارب استخدام سريعة وقابلة للتوسع عبر منصات متعددة.
لكن عند بناء تطبيق عملي، لا تكفي الواجهة وحدها. فأغلب المشاريع تحتاج إلى الاتصال بقاعدة بيانات لجلب البيانات وتحديثها ومزامنتها باستمرار. وهنا تظهر أهمية استخدام Database Hook منسجم مع أسلوب React الحديث.

React Hooks: لماذا غيّرت طريقة تطوير التطبيقات؟
مع إصدار React 16.8، ظهرت Hooks كواحدة من أهم الإضافات في تاريخ المكتبة. فقد أتاحت للمطورين كتابة المكوّنات الوظيفية functional components بطريقة أبسط وأكثر وضوحاً، دون الحاجة إلى الاعتماد على class components في كثير من الحالات.
الجانب الأهم هنا هو أن بعض الـ Hooks تعتمد على ما يسمى dependency array، وهي مصفوفة تراقب القيم المرتبطة بها. وعندما تتغير أي قيمة داخلها، يُعاد تنفيذ المنطق المرتبط بالـ Hook تلقائياً.

أشهر مثال على ذلك هو useEffect:
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
في هذا المثال، سيتم تسجيل قيمة count في حالتين:
- عند تحميل المكوّن لأول مرة.
- عند تغيير القيمة باستخدام
setCount.
هذا المفهوم مهم جداً، لأنه نفس الفكرة التي سنبني عليها إعادة جلب البيانات تلقائياً من قاعدة البيانات داخل التطبيق.
فكرة المقال: Hook يعيد البيانات تلقائياً عند تغيّرها
الهدف هنا هو إنشاء نمط عملي يجعل بياناتك محدثة دائماً، سواء عند تغيّر قيم الاستعلام أو عند تعديل السجلات داخل قاعدة البيانات. باستخدام Easybase وHook باسم useReturn، يمكنك الحصول على سلوك تفاعلي يشبه تماماً أسلوب React في التعامل مع الحالة.
الشكل العام للاستخدام سيكون كالتالي:
const [minRating, setMinRating] = useState(0);
const { frame } = useReturn(
() => /* Your query */,
[minRating]
);
return <div>{frame.map(ele => <Card {...ele} />)}</div>;
في هذا السيناريو، يتم تحديث frame تلقائياً عندما:
- تتغير أي قيمة داخل
dependency arrayمثلminRating. - يتم إنشاء أو تعديل أو حذف بيانات من خلال أي عملية مرتبطة بـ
db.
لماذا يُعد Serverless خياراً مناسباً مع React؟
عند تطوير تطبيقات React أو React Native، يفضّل كثير من المطورين الاعتماد على بنية serverless architecture، خصوصاً في المشاريع الفردية أو فرق العمل الصغيرة. والسبب في ذلك أن هذا الأسلوب يختصر كثيراً من تعقيدات إدارة الخوادم التقليدية.
- توسّع تلقائي حسب الطلب.
- سهولة كبيرة في النشر.
- تقليل أعباء الصيانة وإدارة الخوادم.
- تركيز أكبر على UI/UX.
- خفض التكلفة التشغيلية في المراحل الأولى.
الدمج بين Easybase وReact Hooks يمنحك تجربة تطوير مرنة وحديثة، مع مزامنة مباشرة للبيانات دون إعداد Backend تقليدي معقد.
إعداد مشروع React
إنشاء التطبيق باستخدام create-react-app
إذا لم يكن لديك مشروع جاهز، يمكنك البدء بسرعة عبر create-react-app. يكفي أن يكون لديك node وnpm مثبتين على جهازك.
# npx create-react-app serverless-app
بعد انتهاء الإنشاء، انتقل إلى مجلد المشروع وشغّله:
# cd serverless-app
# npm run start
سيعمل التطبيق محلياً مع ميزة hot reloading، وغالباً سيفتح المتصفح تلقائياً على العنوان http://localhost:3000.

إعداد Easybase وربطه بالتطبيق
تثبيت الحزمة المطلوبة
الخطوة التالية هي تثبيت مكتبة easybase-react داخل المشروع:
npm install easybase-react
هذه هي المكتبة الأساسية التي سنستخدمها في هذا الشرح.
إنشاء جدول في Easybase
بعد إنشاء حساب في easybase.io وتسجيل الدخول، أنشئ جدولاً جديداً باسم MY TABLE وليكن مكوّناً من الأعمدة التالية:
ratingمن النوع numberposterمن النوع imagetitleمن النوع string


بعد ذلك أكمل إنشاء الجدول، ثم افتحه لإضافة سجل تجريبي واحد على الأقل بهدف عرضه لاحقاً داخل التطبيق.


إنشاء Project Token وإعداد الصلاحيات
الآن أنشئ مشروعاً جديداً من داخل واجهة Easybase، لأن ذلك سيمنحك ملف إعدادات يحتوي على بيانات الاتصال الآمنة مع قاعدة البيانات.
مهم: ملف الإعدادات هذا خاص، ويجب عدم مشاركته علناً لأنه يتضمن بيانات وصول إلى مشروعك.

اذهب إلى قسم permissions، ثم فعّل صلاحيات Users not signed in > Read, Write على اسم الجدول.

بعد الحفظ، انتقل إلى تبويب Project Token وقم بتنزيل ملف التهيئة المخصص.

ضع الملف باسم ebconfig.js بجوار App.js ليصبح هيكل المشروع مشابهاً لما يلي:
├ ...
├ ebconfig.js
├ App.css
├ App.js
├ index.js
└ ...
تغليف التطبيق باستخدام EasybaseProvider
افتح الملف src/index.js، ثم استورد EasybaseProvider وملف ebconfig، وبعدها غلّف المكوّن <App /> بهذا المزود:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import ebconfig from './ebconfig';
import { EasybaseProvider } from 'easybase-react';
ReactDOM.render(
<React.StrictMode>
<EasybaseProvider ebconfig={ebconfig}>
<App />
</EasybaseProvider>
</React.StrictMode>,
document.getElementById('root')
);
هذا الأسلوب يُعرف باسم provider pattern، وهو طريقة ممتازة لإتاحة السياق context لجميع مكونات المشروع بطريقة منظمة ومدعومة من React نفسها.
فهم useEasybase: أهم الأدوات التي ستستخدمها
داخل الملف src/App.js يمكنك البدء باستيراد useEasybase واستخراج الأدوات الأساسية التالية:
import { useEasybase } from 'easybase-react';
function App() {
const { useReturn, db, e } = useEasybase();
return ();
}
export default App;
ما وظيفة db؟
الدالة db هي واجهتك المباشرة للتعامل مع قاعدة البيانات. مثال بسيط:
let records = await db('MY TABLE').return().all();
يمكنك استخدامها لجلب السجلات، وإدراج بيانات جديدة، وتحديثها، وحذفها أيضاً.
ما وظيفة e؟
الرمز e يشير إلى expressions، ويُستخدم لبناء شروط الاستعلام داخل db.where. من أمثلته:
e.eqللمساواةe.neqلعدم المساواةe.ltللأصغر منe.gtللأكبر منe.orلبناء شرط ORe.likeلمطابقة النصوص
مثال على استعلام مركب:
let records = await db('MY TABLE')
.return(e.avg('rating'))
.where(
e.or(
e.like('title', 'T%'),
e.lt('rating', 80)
)
)
.all();
هذا الاستعلام يحسب متوسط rating للسجلات التي يبدأ فيها title بالحرف T أو يكون فيها rating أقل من 80.
ما وظيفة useReturn؟
هذا هو الـ Hook الأهم في المقال. فهو يلتف حول db ويجعل البيانات في حالة اشتراك حي مع قاعدة البيانات، بحيث تحصل على مصفوفة حالة اسمها frame يتم تحديثها تلقائياً.
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(
() => db().return()
.where(e.gt('rating', minRating))
.limit(limit),
[minRating, limit]
);
كما يوفر أدوات إضافية مفيدة:
errorللأخطاءloadingلحالة التحميلmanualFetchلإعادة الجلب يدوياًunsubscribeلإلغاء الاشتراك
تنبيه: داخل useReturn لا تستخدم .all أو .one، لأن الـ Hook يتولى ذلك تلقائياً.
بناء أول مكوّن يعرض البيانات من قاعدة البيانات
لنبدأ بعرض محتوى الجدول مباشرة داخل التطبيق:
import { useEasybase } from "easybase-react";
function App() {
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(() => db("MY TABLE").return(), []);
return (
<div>
{frame.map(ele => JSON.stringify(ele))}
</div>
);
}
export default App;
ستُعرض السجلات النصية القادمة من قاعدة البيانات مباشرة داخل الصفحة.

تحسين واجهة العرض عبر مكوّن Card
عرض JSON الخام ليس مناسباً للمستخدم النهائي، لذا من الأفضل إنشاء مكوّن واجهي يعرض السجل بشكل بصري منظم:
function Card({ rating, poster, title, _key }) {
const cardStyle = {
display: "inline-block",
margin: 10,
padding: 10,
borderRadius: 10,
background: "#eaeaea",
minWidth: 200,
};
return (
<div style={cardStyle}>
<img src={poster} style={{ height: 300, minWidth: 200 }} />
<h2>{title}</h2>
<h4>Rating: {rating}</h4>
</div>
);
}
function App() {
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(() => db("MY TABLE").return(), []);
return (
<div style={{ textAlign: "center", display: "inline-block" }}>
{frame.map(ele => <Card {...ele} />)}
</div>
);
}

لاحظ أن كل سجل يحتوي أيضاً على الخاصية _key، وهي معرف فريد لكل صف. هذه القيمة مهمة جداً عند تحديث السجل أو حذفِه أو ربط صورة به.
إدراج سجل جديد في قاعدة البيانات
إضافة زر Add Card
لنضف الآن زراً لإنشاء سجل جديد:
function AddCardButton() {
const addCardStyle = {
background: "#ea55aa",
display: "inline-block",
width: 200,
borderRadius: 10,
cursor: "pointer",
};
return (
<div style={addCardStyle}>
<h2 style={{ color: "#fff" }}>Add Card</h2>
</div>
);
}
function App() {
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(() => db("MY TABLE").return(), []);
return (
<div style={{ textAlign: "center", display: "inline-block" }}>
{frame.map(ele => <Card {...ele} />)}
<AddCardButton />
</div>
);
}

إرسال البيانات إلى قاعدة البيانات
يمكنك جمع المدخلات بعدة طرق مثل النماذج أو الحوارات أو prompt. لأغراض الشرح السريع، سنستخدم prompt:
function AddCardButton() {
const { db } = useEasybase();
async function addCardClick() {
let title = prompt("Please enter a movie title");
let rating = prompt("Please enter the rating for this movie");
if (!rating || !title) {
return;
}
db("MY TABLE")
.insert({ title, rating: Number(rating) })
.one();
}
return (
<div style={addCardStyle} onClick={addCardClick}>
<h2 style={{ color: "#fff" }}>Add Card</h2>
</div>
);
}
بمجرد تنفيذ الإدراج، ستلاحظ أن useReturn يعيد تحديث frame تلقائياً دون الحاجة إلى إعادة تحميل الصفحة يدوياً.


رفع الصور وربطها بالسجلات
القيم النصية والرقمية يمكن إدراجها مباشرة، لكن الوسائط مثل الصور والفيديو والملفات تحتاج إلى رفع upload منفصل. لهذا الغرض توفر Easybase الدالة setImage.
سنعود إلى مكوّن Card ونضيف إمكانية رفع صورة عند النقر على مساحة الصورة:
function Card({ rating, poster, title, _key }) {
const { setImage } = useEasybase();
async function onFileChange(e) {
if (e.target.files[0]) {
await setImage(_key, "poster", e.target.files[0], "MY TABLE");
}
}
return (
<div style={cardStyle}>
<input
id={"fileInput" + _key}
hidden
type="file"
onChange={onFileChange}
/>
<img
src={poster}
style={{ height: 300, minWidth: 200 }}
onClick={_ => document.getElementById("fileInput" + _key).click()}
/>
<h2>{title}</h2>
<h4>Rating: {rating}</h4>
</div>
);
}
باستخدام هذا الأسلوب، يتحول عنصر الصورة إلى واجهة لفتح منتقي الملفات، وعند اختيار صورة يتم رفعها وربطها بالسجل المحدد عبر _key.

بعد الرفع، تصبح الصورة متاحة عبر Easybase CDN، وتظهر مباشرة داخل frame بفضل التحديث التلقائي.

استخدام dependency array لبناء استعلامات ديناميكية
من أقوى مزايا useReturn أنه لا يكتفي بمراقبة قاعدة البيانات نفسها، بل يمكنه أيضاً إعادة تنفيذ الاستعلام عند تغيّر قيم معينة في التطبيق. وهذا مثالي لبناء فلاتر بحث حية.
إضافة حقل تصفية حسب التقييم
import { useEasybase } from "easybase-react";
function App() {
const [ratingMin, setRatingMin] = useState(0);
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(() => db("MY TABLE").return(), []);
return (
<div>
<div style={{ textAlign: "center", display: "inline-block" }}>
{frame.map(ele => <Card {...ele} />)}
<AddCardButton />
</div>
<p>
Rating filter:
<input
type="number"
value={ratingMin}
onChange={e => setRatingMin(Number(e.target.value))}
/>
</p>
</div>
);
}

ربط التصفية بالاستعلام مباشرة
كل ما عليك فعله الآن هو استخدام ratingMin داخل الاستعلام، ووضعه أيضاً داخل dependency array:
function App() {
const [ratingMin, setRatingMin] = useState(0);
const { useReturn, db, e } = useEasybase();
const { frame } = useReturn(
() => db("MY TABLE").return().where(e.gte("rating", ratingMin)),
[ratingMin]
);
// ...
}
هنا نستخدم e.gte("rating", ratingMin) لجلب السجلات التي يكون فيها rating أكبر من أو يساوي الحد الأدنى المحدد.

وكل السجلات المضافة أو المعدلة ستبقى متزامنة مع قاعدة البيانات البعيدة.

أفضل الممارسات عند التعامل مع useReturn وEasybase
- استخدم
dependency arrayفقط للقيم التي تؤثر فعلاً في الاستعلام. - لا تضع
.allأو.oneداخلuseReturn. - احرص على حماية ملف
ebconfig.jsوعدم نشره علناً. - استخدم
.limitو.offsetعند التعامل مع أعداد كبيرة من السجلات. - افصل المكونات مثل
CardوAddCardButtonفي ملفات مستقلة لتحسين تنظيم المشروع.
مزايا إضافية تستحق الانتباه
مكتبة easybase-react لا تقتصر على الاستعلامات فقط، بل توفر إمكانات أخرى مهمة مثل:
- إدارة المستخدمين والمصادقة authentication.
- دوال تجميع aggregators مثل
e.minوe.maxوe.avg. - التعامل مع الأحداث عبر
dbEventListenerلتنفيذ منطق إضافي كلما تم تنفيذ استعلام. - دعم مناسب لـ React Native أيضاً.
الخلاصة التقنية
إذا كنت تبحث عن طريقة عملية لبناء طبقة بيانات تفاعلية داخل React دون تعقيد Backend تقليدي، فإن الجمع بين useReturn وEasybase يوفّر نموذجاً فعالاً وسهل الفهم. الفكرة الجوهرية هنا ليست مجرد جلب البيانات، بل جعلها جزءاً حياً من دورة حياة التطبيق، بحيث تتحدث الواجهة تلقائياً عند تغيّر المدخلات أو السجلات. من منظور تقني، هذا النهج مناسب جداً لتطبيقات MVP ولوحات التحكم والتطبيقات السريعة التي تحتاج إلى مزامنة مباشرة وتجربة تطوير مرنة وقابلة للتوسع.