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 - خليه قريب من الـ
Outlet
component - تأكد إنه موجود مرة واحدة بس في التطبيق
- متحطوش في components فرعية أو صفحات منفصلة
مثال لهيكل ملفات مثالي 📁
src/
├── main.jsx # entry point
├── App.jsx # فيه RouterProvider
├── layouts/
│ └── RootLayout.jsx # ✅ حط ScrollRestoration هنا
└── pages/
├── HomePage.jsx # ❌ مش هنا
└── AboutPage.jsx # ❌ ولا هنا
- لو محتاج تخصص سلوك الـscroll، استخدم الحل التاني
- اختبر التطبيق على مختلف المتصفحات علشان تتأكد إن كل حاجة شغالة تمام
الخلاصة ✨
- استخدم
ScrollRestoration
للحل السريع والبسيط - استخدم
useLayoutEffect
لو محتاج تحكم أكتر - فكر في تجربة المستخدم واختار الحل المناسب لتطبيقك