@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[]whenenableAdminAccessis off).listAdminLessons(params?, options?)— on-demand fetch with its ownoffset/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.