@msflib/react-categories¶
Standalone categories module for React applications, built on TanStack Query v5 and integrated with @msflib/core + @msflib/react-shared.
This package provides:
- A strongly typed categories scope provider (CategoryProvider)
- A hook for consuming a single category type with optional query params (useCategories)
- A compatibility alias for older code paths (useCategory)
- A direct API factory (createCategoryApi) for non-context usage
- Exported query keys (categoriesQueryKeys) for cache invalidation from consuming apps
- End-to-end TypeScript types for payloads, responses, and context contracts
Table of Contents¶
- Installation
- Peer Dependencies
- What This Module Covers
- Quick Start
- Configuration
- Base Application Configuration
- Categories Endpoint Override
- API Reference
- CategoryProvider
- useCategories
- useCategory
- createCategoryApi
- categoriesQueryKeys
- TypeScript Reference
- Endpoint Defaults and Resolution
- Caching Behavior
- Multi-Tenancy Behavior
- Usage Recipes
- Testing
- Troubleshooting
- Public Exports
Installation¶
pnpm add @msflib/react-categories @msflib/core @msflib/react-shared @tanstack/react-query
Peer Dependencies¶
| Package | Version |
|---|---|
| react | ^18 || ^19 |
| react-dom | ^18 || ^19 |
| @tanstack/react-query | ^5 |
| @msflib/core | workspace:* |
| @msflib/react-shared | workspace:* |
What This Module Covers¶
The categories module wraps these backend operations:
- GET cat/{category_type}
- POST cat/{category_type}
- PUT cat/{category_type}/{category_id}
- DELETE cat/{category_type}/{category_id}
At runtime, these route calls are executed through configuredApiClient from @msflib/core, which applies workspace-scoping behavior consistently with the rest of the ecosystem.
Quick Start¶
1) Configure your app once¶
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'access_token',
endpoints: {
categories: {
categories: '/cat',
},
},
});
2) Wrap your app with QueryClient + CategoryProvider¶
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { CategoryProvider } from '@msflib/react-categories';
const queryClient = new QueryClient();
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<CategoryProvider
categoryTypes={['track', 'stage', 'level']}
options={{
query: { offset: 0, limit: 100 },
}}
>
{children}
</CategoryProvider>
</QueryClientProvider>
);
}
3) Consume one category type in a screen¶
import { useCategories } from '@msflib/react-categories';
export function TrackList() {
const {
categories,
createCategory,
updateCategory,
deleteCategory,
loading,
} = useCategories({
categoryType: 'track',
query: { offset: 0, limit: 20 },
});
return (
<div>
<p>Total categories: {categories.length}</p>
<button
disabled={loading.create}
onClick={() =>
createCategory({
name: 'frontend',
title: 'Frontend',
description: 'Frontend topics',
tags: ['react'],
order: 1,
})
}
>
Create
</button>
<button
disabled={loading.update}
onClick={() => updateCategory(1, { title: 'Updated Frontend' })}
>
Update
</button>
<button disabled={loading.delete} onClick={() => deleteCategory(1)}>
Delete
</button>
</div>
);
}
Configuration¶
Base Application Configuration¶
@msflib/react-categories expects @msflib/core application config to be initialized before provider usage.
import { configureApplication } from '@msflib/core';
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'access_token',
workspace: 'workspace-a',
});
Categories Endpoint Override¶
By default the module uses /cat. Override it if your API namespace differs:
configureApplication({
baseURL: 'https://api.example.com',
accessTokenKey: 'access_token',
endpoints: {
categories: {
categories: '/v2/categories',
},
},
});
API Reference¶
CategoryProvider¶
Context provider for the stable category scope. It also primes TanStack Query with the provider-level default query for each configured category type.
import { CategoryProvider } from '@msflib/react-categories';
<CategoryProvider
categoryTypes={['track', 'stage', 'level']}
options={{
isWorkspaceScoped: true,
requireAuth: true,
isAuthenticated: true,
query: { offset: 0, limit: 100 },
}}
>
{children}
</CategoryProvider>;
Props¶
| Prop | Type | Required | Description |
|---|---|---|---|
| children | React.ReactNode | Yes | Descendant tree that will consume category hooks. |
| categoryTypes | string[] | No | Category namespaces managed by this provider. Use this for the multi-category setup. |
| categoryType | string | No | Backward-compatible single-type fallback if categoryTypes is not supplied. |
| options.isWorkspaceScoped | boolean | No | Controls whether endpoints are workspace-scoped. Default: true. |
| options.requireAuth | boolean | No | If true, queries only run when isAuthenticated is true. Default: false. |
| options.isAuthenticated | boolean | No | Auth gate flag used only when requireAuth is true. |
| options.query | object | No | Default list query applied by hooks unless overridden at the hook call site. |
Behavior¶
- The provider defines the stable category scope and default query config.
- The hook decides which category type to load and which query params to use.
- Mutations invalidate the matching category type + workspace cache scope.
useCategories¶
Hook that returns data and actions for one category type:
import { useCategories } from '@msflib/react-categories';
const {
categories,
createCategory,
updateCategory,
deleteCategory,
refetch,
loading,
} = useCategories({
categoryType: 'track',
query: { offset: 0, limit: 20 },
});
If used outside CategoryProvider, it throws:
useCategories must be used inside <CategoryProvider />
useCategory¶
Backward-compatible alias for useCategories.
import { useCategory } from '@msflib/react-categories';
const trackCategories = useCategory({ categoryType: 'track' });
createCategoryApi¶
Factory for direct API usage (without context).
import { createCategoryApi } from '@msflib/react-categories';
const categoryApi = createCategoryApi('track', true); // isWorkspaceScoped = true by default
const list = await categoryApi.list();
await categoryApi.create({
name: 'frontend',
title: 'Frontend',
description: 'Frontend topics',
tags: ['react'],
order: 1,
});
await categoryApi.update(10, { title: 'Updated Frontend' });
await categoryApi.delete(10);
Signature¶
createCategoryApi(categoryType: string, isWorkspaceScoped?: boolean)
categoriesQueryKeys¶
Exported helper for category query cache key generation.
import { categoriesQueryKeys } from '@msflib/react-categories';
categoriesQueryKeys.all();
categoriesQueryKeys.scope('track', 'workspace-slug');
Use these keys from consuming apps for invalidation:
import { categoriesQueryKeys } from '@msflib/react-categories';
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
await queryClient.invalidateQueries({
queryKey: categoriesQueryKeys.scope('track', 'workspace-slug'),
});
TypeScript Reference¶
Category¶
Represents the category entity returned by list/create/update/delete.
Core fields:
- id: number
- name: string
- title: string
- description: string
- tags: string[]
- order: number
It also supports extra backend fields through an index signature.
CategoryPayload¶
Required:
- name: string
- title: string
- description: string
- tags: string[]
- order: number
UpdateCategoryPayload¶
Partial category update payload excluding name.
CategoryConsumerContextType¶
Includes:
- categoryType: string
- query?: CategoryListParams
- categories: Category[]
- createCategory(data, options?)
- updateCategory(categoryId, data, options?)
- deleteCategory(categoryId, options?)
- refetch()
- loading flags for all operations
Mutation option parameters use TanStack MutateOptions for per-call success/error handlers.
Endpoint Defaults and Resolution¶
Default categories base endpoint:
/cat
Resolved endpoints:
- GET /cat/:categoryType
- POST /cat/:categoryType
- PUT /cat/:categoryType/:categoryId
- DELETE /cat/:categoryType/:categoryId
Path generation and workspace scoping are delegated to the core configured API client, so workspace prefixes are applied consistently with your global core configuration.
Caching Behavior¶
- categories query key: categoriesQueryKeys.list(categoryType, activeWorkspaceOrNull, query)
- categories query is stale for 60 seconds (staleTime: 60_000)
- createCategory, updateCategory, deleteCategory all invalidate the same category scope
Multi-Tenancy Behavior¶
- Provider reads current workspace from useActiveWorkspace().
- When options.isWorkspaceScoped === false, workspace value for query-keying is treated as null.
This enables downstream modules relying on active workspace to react to workspace changes.
Usage Recipes¶
Auth-gated categories query¶
<CategoryProvider
categoryTypes={['track', 'stage', 'level']}
options={{
requireAuth: true,
isAuthenticated,
}}
>
{children}
</CategoryProvider>
When requireAuth is true and isAuthenticated is false, the initial categories request is not executed.
Using mutation callbacks¶
await createCategory(
{
name: 'frontend',
title: 'Frontend',
description: 'Frontend topics',
tags: ['react'],
order: 1,
},
{
onSuccess: (category) => {
console.log('Created category id:', category.id);
},
onError: (error) => {
console.error(error.message);
},
},
);
Manual refresh¶
const { refetch } = useCategory({ categoryType: 'track' });
refetch();
Testing¶
Run package tests:
pnpm --filter @msflib/react-categories test:build
The package includes tests for:
- API endpoint construction and client interaction
- Provider query and mutation behavior
- Auth-gated query execution
- Hook/provider contract smoke coverage
Troubleshooting¶
useCategory must be used inside ¶
Cause: hook is called outside provider scope.
Fix: ensure component is rendered under CategoryProvider.
categories not fetching¶
Check:
- QueryClientProvider is present
- CategoryProvider is mounted with categoryTypes
- If using auth gating: requireAuth: true and isAuthenticated must be true
Wrong endpoint path or missing workspace prefix¶
Check:
- configureApplication(...) ran before provider/API usage
- endpoints.categories.categories override is correct
- isWorkspaceScoped option matches your intended path mode
Public Exports¶
From @msflib/react-categories:
- Context and provider:
- CategoryProvider
- CategoryContext
- Hook:
- useCategory
- API:
- createCategoryApi
- Query keys:
- categoriesQueryKeys
- Types:
- Category
- CategoryPayload
- UpdateCategoryPayload
- CategoryConsumerContextType