660 words
3 minutes
Scroll Problem in React Router
ازاي تتحكم في الـscroll لما تنتقل بين الصفحات في React Router 🚀
المشكلة 🤔
لو بتستخدم React Router، أكيد قابلتك المشكلة دي قبل كده: لما بتنتقل من صفحة لصفحة، الـscroll مش بيرجع لفوق تلقائي. يعني لو كنت في نص الصفحة وضغطت على لينك يوديك لصفحة تانية، الصفحة الجديدة هتفتح في نفس المكان - مش من فوق.
ليه المشكلة دي بتحصل؟ 🧐
علشان نفهم المشكلة كويس، لازم نعرف شوية حاجات عن React Router والـSingle Page Applications (SPA)
- React Router بيتعامل مع التطبيق كـSPA، يعني صفحة واحدة بتتغير محتواها
- مفيش refresh للصفحة لما بننتقل بين الـroutes
- React Router مش بيغير الـscroll position تلقائي، علشان
- ممكن تكون عايز ترجع لنفس المكان لما تدخل على الصفحة تاني
- ده السلوك الافتراضي في الـSPA
الحلول المتاحة 💡
الحل الأول: استخدام ScrollRestoration من React Router 📜
ده أبسط حل وأسرع طريقة. كل اللي عليك تعمله إنك تضيف الـcomponent ده في الـroot component بتاع التطبيق
import { ScrollRestoration } from 'react-router-dom';
function Root() {
return (
<>
<nav>
{/* قائمة التنقل بتاعتك */}
</nav>
<Outlet />
<ScrollRestoration />
</>
);
}
الحل التاني: استخدام useLayoutEffect و scrollTo 🔄
الحل ده مناسب لو عايز:
- تتحكم أكتر في سلوك الـscroll
- تمنع أي انيميشن للـscroll
- تضمن إن الـscroll يحصل قبل ما المستخدم يشوف الصفحة الجديدة
import { useLayoutEffect } from 'react';
function Page() {
useLayoutEffect(() => {
window.scrollTo({ top: 0, behavior: 'instant' });
}, []);
return (
<div>
{/* محتوى الصفحة */}
</div>
);
}
ليه useLayoutEffect مش useEffect؟ 🤓
في فرق مهم بين الاتنين:
useEffectبيتنفذ بعد ما React يخلص renderinguseLayoutEffectبيتنفذ قبل ما المستخدم يشوف أي تغيير على الشاشة
ده بيمنع حاجة اسمها FOUC (Flash of Unscrolled Content)، يعني المستخدم مش هيشوف الصفحة في المكان القديم قبل ما تتنقل لفوق.
مثال كامل للتطبيق 🌟
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import { ScrollRestoration } from 'react-router-dom';
// Root Component
function Root() {
return (
<>
<nav>
<Link to="/">الرئيسية</Link>
<Link to="/another">صفحة تانية</Link>
</nav>
<Outlet />
<ScrollRestoration />
</>
);
}
// تعريف الـRoutes
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
children: [
{
path: '/',
element: <SomePage />
},
{
path: '/another',
element: <AnotherPage />
}
]
}
]);
// التطبيق الرئيسي
function App() {
return <RouterProvider router={router} />;
}
نصائح إضافية 💪
- تأكد إن الـ
ScrollRestorationموجود في أعلى مستوى في التطبيق يعني إيـه؟
أين تضع الـScrollRestoration في تطبيقك؟ 🎯
المكان الصح ✅
// App.jsx أو root.jsx
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import { ScrollRestoration } from 'react-router-dom';
function RootLayout() {
return (
<div>
<header>
<nav>{/* القائمة بتاعتك */}</nav>
</header>
<ScrollRestoration /> {/* هنا المكان الصح */}
<Outlet />
<footer>{/* الفوتر بتاعك */}</footer>
</div>
);
}
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
children: [
{ path: '/', element: <HomePage /> },
{ path: '/about', element: <AboutPage /> }
]
}
]);
export default function App() {
return <RouterProvider router={router} />;
}
أماكن غلط ❌
// صفحة فرعية - HomePage.jsx
function HomePage() {
return (
<div>
<ScrollRestoration /> {/* ❌ غلط - مش هيشتغل كويس في كل الحالات */}
<h1>الصفحة الرئيسية</h1>
</div>
);
}
// مكون فرعي - Sidebar.jsx
function Sidebar() {
return (
<aside>
<ScrollRestoration /> {/* ❌ غلط - بعيد عن الـrouting logic */}
<nav>{/* محتوى السايدبار */}</nav>
</aside>
);
}
ليه المكان مهم؟ 🤔
الـScope:
ScrollRestorationلازم يكون في مستوى أعلى من كل الـroutes- علشان يقدر يتحكم في الـscroll مع كل تغيير في الـURL
التأثير:
- لما يكون في المستوى الأعلى، هيأثر على كل الصفحات
- هيتأكد إن الـscroll بيرجع لفوق مع كل navigation
الـPerformance:
- وجوده في مكان واحد أفضل من تكراره في كل صفحة
- بيمنع المشاكل اللي ممكن تحصل من وجود أكتر من instance
أفضل الممارسات 📝
- حط الـ
ScrollRestorationفي الـroot layout component - خليه قريب من الـ
Outletcomponent - تأكد إنه موجود مرة واحدة بس في التطبيق
- متحطوش في components فرعية أو صفحات منفصلة
مثال لهيكل ملفات مثالي 📁
src/
├── main.jsx # entry point
├── App.jsx # فيه RouterProvider
├── layouts/
│ └── RootLayout.jsx # ✅ حط ScrollRestoration هنا
└── pages/
├── HomePage.jsx # ❌ مش هنا
└── AboutPage.jsx # ❌ ولا هنا
- لو محتاج تخصص سلوك الـscroll، استخدم الحل التاني
- اختبر التطبيق على مختلف المتصفحات علشان تتأكد إن كل حاجة شغالة تمام
الخلاصة ✨
- استخدم
ScrollRestorationللحل السريع والبسيط - استخدم
useLayoutEffectلو محتاج تحكم أكتر - فكر في تجربة المستخدم واختار الحل المناسب لتطبيقك
