Skip to content

@msflib/react-lesson

Standalone lesson module built on TanStack Query.

Install

pnpm add @msflib/react-lesson

Peer deps

  • react >= 18
  • @tanstack/react-query >= 5

Usage

Wrap your app with the module Provider inside your QueryClientProvider.

In Next.js App Router, Provider must be in a client component ("use client").

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { LessonProvider, useLesson } from '@msflib/react-lesson';

const queryClient = new QueryClient();

<QueryClientProvider client={queryClient}>
  <LessonProvider options={{ requireAuth: true, isAuthenticated: true }}>
    <LessonList />
  </LessonProvider>
</QueryClientProvider>;

function LessonList() {
  const { lessons, loading, updateStudentData } = useLesson();

  if (loading.lessons) return <p>Loading…</p>;

  return (
    <ul>
      {lessons.map((lesson) => (
        <li key={lesson.id}>
          <h3>{lesson.title}</h3>
          <button
            disabled={loading.updateStudentData}
            onClick={() => updateStudentData(lesson.id, { is_completed: true })}
          >
            Mark as complete
          </button>
        </li>
      ))}
    </ul>
  );
}

getLesson(id) does a synchronous local lookup against the cached lessons list — it does not trigger a network request. createLesson/updateLesson accept a FormData object (both endpoints are multipart/form-data, since they accept an optional avatar file upload).

Admin lesson access

Alongside the regular student-facing /lesson routes, the backend exposes /admin/lesson routes for instructor/admin surfaces — a raw lesson listing without per-student student_data, plus the ability to delete any lesson by id. These are off by default; opt in with enableAdminAccess, the same way @msflib/react-tasks gates its promotion/demotion flows:

<LessonProvider options={{ enableAdminAccess: true }}>
  {children}
</LessonProvider>

When enabled, the provider automatically loads GET /admin/lesson?offset=0&limit=100 (override via options.query) and exposes:

const {
  adminLessons,
  listAdminLessons,
  getAdminLesson,
  deleteAdminLesson,
} = useLesson();
  • adminLessons — the current admin listing ([] until loaded, and always [] when enableAdminAccess is off).
  • listAdminLessons(params?, options?) — on-demand fetch with its own offset/limit.
  • getAdminLesson(id, options?) — fetch a single lesson via the admin route.
  • deleteAdminLesson(id, options?) — delete any lesson by id (not scoped to the current student).

For a declarative, cache-backed single-lesson read instead of an imperative call, use the standalone useAdminLesson(lessonId) hook:

import { useAdminLesson } from '@msflib/react-lesson';

function AdminLessonDetail({ lessonId }: { lessonId: number }) {
  const { data: lesson, isPending } = useAdminLesson(lessonId);

  if (isPending) return <p>Loading…</p>;
  return <h2>{lesson?.title}</h2>;
}

useAdminLesson is disabled (no request fired) while lessonId is undefined.

Options

Key Type Default Description
isWorkspaceScoped boolean true If true, lessons are isolated per active workspace.
requireAuth boolean false If true, lessons are only fetched once isAuthenticated is true.
isAuthenticated boolean false The authentication status of the user (e.g. from useAuth().status).
enableAdminAccess boolean false If true, also loads/exposes the /admin/lesson collection (see above).
query object — { offset?, limit? } used for the initial admin lessons fetch.

Endpoints

Configure paths through configureApplication({ endpoints: { lessons } }).

Key Default path Methods / routes
lessons /lesson list (GET), create (POST); detail/update/delete on /{id} (GET/PUT/DELETE); /{id}/student-data (PUT)
adminLessons /admin/lesson list (GET, supports offset/limit); detail/delete on /{id} (GET/DELETE)

Notes: - All lesson endpoints (student-facing and admin) are workspace-scoped. - createLesson/updateLesson use multipart/form-data — pass a FormData object with the lesson fields and optionally an avatar file. Every other endpoint uses application/json. - deleteLesson/deleteAdminLesson resolve with the deleted Lesson, matching the backend response.