alepha@docs:~/docs/guides/frontend$
cat 1-react.md
3 min read
Last commit:

#React Integration

Alepha provides first-class React support with server-side rendering, dependency injection in components, type-safe API calls, and a global state system.

#Project Setup

Scaffold a React project with the Alepha CLI:

bash
alepha init --react

This generates two entry points:

  • src/main.server.ts (or main.ts) -- server entry, registers modules and starts the app
  • src/main.browser.ts -- browser entry, registers browser-side modules and hydrates

Server entry (main.ts):

typescript
 1import { Alepha, run } from "alepha"; 2import { AppRouter } from "./AppRouter.ts"; 3import { CountApi } from "./CountApi.ts"; 4  5const alepha = Alepha.create({ 6  env: { APP_NAME: "MY_APP" }, 7}); 8  9alepha.with(CountApi);10alepha.with(AppRouter);11 12run(alepha);

Browser entry (main.browser.ts):

typescript
1import { Alepha, run } from "alepha";2import { AppRouter } from "./AppRouter.ts";3 4const alepha = Alepha.create();5alepha.with(AppRouter);6 7run(alepha);

The browser entry only registers the modules needed on the client (e.g., routes, UI). Server-only modules like API controllers are excluded.

#Core Hooks

All core React hooks are imported from "alepha/react".

#useAlepha

Returns the current Alepha instance from context. Provides access to the DI container, event system, and store.

typescript
1import { useAlepha } from "alepha/react";2 3function MyComponent() {4  const alepha = useAlepha();5  // alepha.inject(SomeService)6  // alepha.events.emit(...)7  // alepha.store.get(...)8}

Must be used within an Alepha context (provided automatically by the router or by <AlephaProvider>).

#useInject

Injects a DI service into a React component. The service must be registered with the Alepha instance. Result is memoized.

typescript
1import { useInject } from "alepha/react";2 3function Dashboard() {4  const analytics = useInject(AnalyticsService);5  // use analytics methods6}

#useClient

Type-safe API calls from React. Connects to server-side controllers via the link system. Works with SSR -- on the server, calls are made internally without HTTP.

typescript
 1import { useAction, useClient } from "alepha/react"; 2import { useState } from "react"; 3import type { CountApi } from "./CountApi.ts"; 4  5const Home = (props: { count: number }) => { 6  const [count, setCount] = useState(props.count); 7  const countApi = useClient<CountApi>(); 8  9  const inc = useAction(10    {11      handler: async () => {12        const result = await countApi.inc();13        setCount(result.count);14      },15    },16    [count],17  );18 19  return <button onClick={inc.run}>Click {count}</button>;20};

The type parameter <CountApi> provides full type safety -- method names, parameter types, and return types are all inferred from the controller class.

#useAction

Manages async operations with loading state, error handling, cancellation, debounce, and polling.

typescript
1import { useAction } from "alepha/react";

Returns: { run, loading, error, cancel, result }

Property Type Description
run (...args) => Promise Execute the action
loading boolean True while executing
error Error | undefined Error from last failed execution
cancel () => void Cancel debounce timer or abort in-flight
result T | undefined Result from last successful execution

Options:

Option Type Description
handler (...args, ctx) => Promise The async function to execute. Receives an ActionContext with an AbortSignal as the last argument.
onError (error) => void Custom error handler. Prevents re-throw if set.
onSuccess (result) => void Called after successful execution.
id string Identifier for debugging and analytics.
debounce number Delay in milliseconds before executing.
runOnInit boolean Run once when the component mounts.
runEvery DurationLike Run periodically at the given interval.

By default, concurrent executions are prevented -- calling run while already executing is a no-op.

Debounce example (search input):

typescript
 1const search = useAction( 2  { 3    handler: async (query: string) => { 4      return await api.search(query); 5    }, 6    debounce: 300, 7  }, 8  [], 9);10 11// <input onChange={(e) => search.run(e.target.value)} />

Polling example:

typescript
 1const pollStatus = useAction( 2  { 3    handler: async () => { 4      return await api.getStatus(); 5    }, 6    runEvery: 5000, 7  }, 8  [], 9);10 11// Or with duration tuple:12// runEvery: [30, "seconds"]

AbortController example:

typescript
 1const fetchData = useAction( 2  { 3    handler: async (url: string, { signal }: { signal: AbortSignal }) => { 4      const response = await fetch(url, { signal }); 5      return response.json(); 6    }, 7  }, 8  [], 9);10// Automatically cancelled on unmount or when a new request starts

Lifecycle events:

Actions emit events on the Alepha event system:

  • react:action:begin -- action started
  • react:action:success -- action completed successfully
  • react:action:error -- action threw an error
  • react:action:end -- always emitted at the end

Global error handling example:

typescript
1alepha.events.on("react:action:error", ({ error }) => {2  toast.danger(error.message);3});

#useEvents

Subscribe to Alepha events inside React components. Subscriptions are automatically cleaned up on unmount.

typescript
 1import { useEvents } from "alepha/react"; 2  3function StatusBar() { 4  useEvents( 5    { 6      "react:transition:begin": (ev) => { 7        console.log("Navigating to:", ev.state.url.pathname); 8      }, 9      "react:action:error": (ev) => {10        console.error("Action failed:", ev.error);11      },12    },13    [],14  );15 16  return <div>...</div>;17}

The second argument is a dependency list (same as useEffect). Events are fully typed based on the Hooks interface.

#AlephaProvider

Only needed if you are not using the Alepha Router (e.g., in Expo or Next.js integrations). When using $page and the router, the context is provided automatically.

typescript
 1import { AlephaProvider } from "alepha/react"; 2  3function App() { 4  return ( 5    <AlephaProvider 6      onLoading={() => <div>Loading...</div>} 7      onError={(error) => <div>Error: {error.message}</div>} 8    > 9      <MyApp />10    </AlephaProvider>11  );12}

AlephaProvider creates an Alepha instance, calls start(), and provides the instance via React context. Props:

Prop Type Description
children ReactNode Application content
onLoading () => ReactNode Rendered while Alepha is starting
onError (error: Error) => ReactNode Rendered if start fails