• בלוג
  • שילוב React Router Data API עם RTK Query

שילוב React Router Data API עם RTK Query

16/06/2023

אחד השילובים האהובים עליי בתקופה האחרונה הוא החיבור בין Data API של React Router ל RTK Query. בקצרה, ה Data API אומר שאנחנו יכולים להוציא את כל הלוגיקה של "מתי" מידע נטען מהשרת ל Router במקום שיישמר בקומפוננטות, ו RTK Query יודע למדל טוב את הלוגיקה של "איך" מידע נטען מהשרת. יחד הם שילוב מנצח כפי שנראה בפוסט הבא.

1. הגדרת ה API דרך RTK Query

אני מתחיל עם הקובץ store.js ושם מגדיר את נקודות הקצה ל API. בשביל הדוגמה ניקח מערכת מידע שמחזיקה שתי נקודות קצה, אחת מחזירה רשימת משימות לביצוע והשניה פרטים לגבי משימה ספציפית. זה הקוד:

export const tasksApi = createApi({
  reducerPath: 'tasksApi',  
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:4000' }),
  endpoints: (builder) => ({
    getTasks: builder.query({
      query: () => `tasks`,
    }),
    getTask: builder.query({
      query: (id) => `tasks/${id}`,
    }),
  }),
})

הגדרת ה API מאוד פשוטה אבל כן יש לנו את כל הגמישות של RTK Query, למשל לקבוע כמה זמן מידע יישמר ב Cache, טרנספורמציות על המידע ויכולות נוספות מסוג זה.

2. פניה ל API מתוך ה Router

ריאקט ראוטר מאפשר לשלוח בקשת רשת כשאנחנו נכנסים לדף מסוים, ויודע לאסוף את כל הבקשות שקשורות לדפים מקוננים. לכן בהגדרת הניתוב הבאה בכניסה למסך של משימה ספציפית בעצם יישלחו שתי בקשות רשת:


const router = createBrowserRouter([
  {
    element: <Root />,
    children: [
      {
        path: '/',
        element: <Home />
      },
      {
        path: '/tasks',
        element: <TasksPage />,
        loader: async () => json((await store.dispatch(tasksApi.endpoints.getTasks.initiate())).data),
        errorElement: <p>Error Loading Tasks List</p>,
        children: [
          {
            path: '',
            index: true,
            element: <p>I'm The Index</p>
          },
          {
            path: 'new',
            element: <NewTask />
          },
          {
            path: ':id',
            element: <ViewTask />,
            loader: async ({params}) => json((await store.dispatch(tasksApi.endpoints.getTask.initiate(params.id))).data),
          }
        ],
      }
    ]
  }
]);

והשורות המעניינות הן הגדרת ה Loaders, לדוגמה:

loader: async ({params}) => json((await store.dispatch(tasksApi.endpoints.getTask.initiate(params.id))).data),

ה Router מגדיר "מתי" בקשה צריכה להישלח, כלומר בכניסה לדף המשימות. ה RTK Query מגדיר "איך" בקשה נשלחת באמצעות מימוש הפונקציה getTask.

מאחר וגם store וגם tasksApi הם גלובאליים, אין בעיה להשתמש בהם גם מחוץ לקומפוננטות.

3. קומפוננטה מקבלת מידע

ברגע שסיימנו לחבר את כל המכניקה של שליפות מידע מתוך ה Router ו RTK Query, לקומפוננטות כבר לא נשארה עבודה. זה הקוד שמאפשר לקומפוננטת מסך המשימות גישה למידע:

export function TasksPage() {
  const tasks = useLoaderData();

  return (
    <div>
      <h2>{tasks.length} Tasks:</h2>
      <div className="tasks-page">
        <div className="left">
          <TasksList tasks={tasks} />
        </div>
        <div className="right">
          <Outlet />
        </div>
      </div>
    </div>
  );
}

כבר לא צריך להגדיר את ה Endpoint ושינוי של ה API יהיה שקוף לגמרי מבחינת הקומפוננטה. אותו דבר בקומפוננטת צפיה במשימה ספציפית:

export function ViewTask() {
  const { id } = useParams();
  const task = useLoaderData();

  if (!task) {
    return <p>Task {id} not found</p>
  }

  return (
    <div>
      <p>{JSON.stringify(task)}</p>
      <p>{task.text}</p>
      <p>Task is {task.done ? "Done" : "Not Done"}</p>
    </div>
  );
}

שלא צריכה כבר לדעת אפילו מה מזהה המשימה שהיא צריכה למשוך, כי כל המידע הזה מנוהל בין ה Router ל RTK Query.