Providers Guide
MIDDAG React ships five providers that bridge backend data into React context. Each reads from Inertia shared props and exposes typed hooks.
I18nProvider
Translation system that reads pre-loaded strings from Inertia shared props (theme.strings) and optionally resolves missing keys via an async resolver injected by the host.
| Prop | Type | Required | Description |
|---|---|---|---|
children | ReactNode | Yes | App tree to provide translations to. |
asyncResolver | (key: string, component?: string) => Promise<string> | No | Optional async function to resolve strings not in the pre-loaded set. |
useTranslation()
Returns t (sync lookup) and tAsync (async lookup with resolver fallback).
import { useTranslation } from '@middag-io/react';
function MyComponent() {
const { t, tAsync } = useTranslation();
// Sync -- returns key itself if not pre-loaded
const title = t('dashboard_title');
// Async -- calls resolver if key is missing
useEffect(() => {
tAsync('rare_string', 'local_myplugin')
.then(setLabel);
}, [tAsync]);
return <h1>{title}</h1>;
}Host-specific resolvers
// Moodle -- uses core/str.get_string()
const moodleResolver = async (key: string, component?: string) => {
const { get_string } = await import('core/str');
return get_string(key, component || 'local_yourplugin');
};
<I18nProvider asyncResolver={moodleResolver}>...</I18nProvider>// WordPress -- uses wp.i18n.__()
const wpResolver = async (key: string) => {
return wp.i18n.__(key, 'your-plugin-textdomain');
};
<I18nProvider asyncResolver={wpResolver}>...</I18nProvider>AuthProvider
Reads the auth object from Inertia shared props and provides user data with capability-based access control.
useAuth()
Returns the authenticated user object and a can() function for capability checks.
import { useAuth } from '@middag-io/react';
function UserGreeting() {
const { user, can } = useAuth();
return (
<div>
<p>Hello, {user.name}</p>
{can('local/middag:manage') && (
<a href="/settings">Settings</a>
)}
</div>
);
}<Can> / <Cannot>
Declarative components for conditional rendering based on capabilities:
import { Can, Cannot } from '@middag-io/react';
function ActionBar() {
return (
<>
<Can capability="local/middag:manage">
<button>Delete</button>
</Can>
<Cannot capability="local/middag:manage">
<p>You do not have permission to manage.</p>
</Cannot>
</>
);
}Auth data shape
| Field | Type | Description |
|---|---|---|
user | { id, name, email, avatarUrl? } | Authenticated user object from Inertia shared props. |
capabilities | string[] | List of Moodle/WP capabilities the user holds. |
can(capability) | (cap: string) => boolean | Returns true if the user has the specified capability. |
FlashProvider
Auto-converts flash shared props into Sonner toast notifications. Supports four severity levels: success, error, info, and warning.
Zero configuration
Just wrap your app with <FlashProvider> and flash messages from the backend will appear as toasts automatically. No additional setup is needed.
// Backend (PHP) -- set flash messages
$this->flash('success', 'Record saved successfully.');
$this->flash('error', 'Failed to delete the record.');
// Frontend -- FlashProvider reads flash prop on each
// Inertia navigation and fires the appropriate toast.
// No code needed in your components.Flash supports a structured toast variant for custom duration:
// Backend
$this->flash('toast', [
'severity' => 'info',
'message' => 'Processing will take a few minutes.',
'duration' => 10000, // 10 seconds
]);ScopeProvider
Dynamic context provider that reads the scope Inertia shared prop. Extensions register scope data during PHP boot() via global_scope_manager::set().
useScope()
Returns the full scope object -- a key-value map of extension-provided data.
import { useScope } from '@middag-io/react';
function OrgBanner() {
const scope = useScope();
const org = scope.organization as {
id: number;
name: string;
} | undefined;
if (!org) return null;
return <div>Organization: {org.name}</div>;
}useScopeKey<T>(key)
Type-safe accessor for a single scope key:
import { useScopeKey } from '@middag-io/react';
interface OrgScope {
id: number;
name: string;
slug: string;
}
function OrgHeader() {
const org = useScopeKey<OrgScope>('organization');
return <h2>{org?.name ?? 'No organization'}</h2>;
}Setting scope from PHP
// In your extension's boot() method:
$scope = $this->container->get(global_scope_manager::class);
$scope->set('organization', [
'id' => $org->id,
'name' => $org->name,
'slug' => $org->slug,
]);ProgressProvider
Displays a thin NProgress bar at the top of the viewport during Inertia page transitions. Hooks into Inertia router events automatically.
No configuration needed
Just wrap your app with <ProgressProvider>. It listens to router.on('start') and router.on('finish') events from Inertia and manages the progress bar state.
Behavior on navigation outcomes:
- Completed -- bar finishes and fades out
- Interrupted -- bar resets to zero instantly
- Failed/Cancelled -- bar completes with a brief red flash (uses
var(--destructive)color)
Provider composition
Typical provider stack in your app root:
import {
I18nProvider,
AuthProvider,
FlashProvider,
ScopeProvider,
ProgressProvider,
} from '@middag-io/react';
function App({ children }) {
return (
<I18nProvider asyncResolver={resolver}>
<AuthProvider>
<ScopeProvider>
<FlashProvider>
<ProgressProvider>
{children}
</ProgressProvider>
</FlashProvider>
</ScopeProvider>
</AuthProvider>
</I18nProvider>
);
}