Skip to content

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.

PropTypeRequiredDescription
childrenReactNodeYesApp tree to provide translations to.
asyncResolver(key: string, component?: string) => Promise<string>NoOptional async function to resolve strings not in the pre-loaded set.

useTranslation()

Returns t (sync lookup) and tAsync (async lookup with resolver fallback).

tsx
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

tsx
// 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>
tsx
// 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.

tsx
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:

tsx
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

FieldTypeDescription
user{ id, name, email, avatarUrl? }Authenticated user object from Inertia shared props.
capabilitiesstring[]List of Moodle/WP capabilities the user holds.
can(capability)(cap: string) => booleanReturns 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.

php
// 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:

php
// 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.

tsx
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:

tsx
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

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:

tsx
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>
    );
}

Released under the proprietary license.