5.4 KiB
sidebar_position, title
| sidebar_position | title |
|---|---|
| 1 | Getting Started |
Getting Started
effect-fc lets React components be written as Effect programs. Inside a
component body you can yield services, run Effects, subscribe to Effect-powered
state, and still export a normal React function component at the edge of your
app.
This guide starts with the smallest useful setup:
- Install
effect-fcwith its peer dependencies. - Create a React runtime from an Effect
Layer. - Wrap your React app with
ReactRuntime.Provider. - Write a component with
Component.make. - Convert it to a React component with
Component.withRuntime.
Install
Install effect-fc alongside effect and React 19.2 or newer:
npm install effect-fc effect react react-dom
If your project uses TypeScript, also install React's type packages:
npm install --save-dev @types/react @types/react-dom
Create A Runtime
An Effect-FC app needs an Effect runtime. Build one from the services your UI
needs, then share it with React through ReactRuntime.Provider.
For an empty app, Layer.empty is enough:
import { Layer } from "effect"
import { ReactRuntime } from "effect-fc"
export const runtime = ReactRuntime.make(Layer.empty)
As your app grows, add services to the layer:
import { FetchHttpClient } from "@effect/platform"
import { Layer } from "effect"
import { ReactRuntime } from "effect-fc"
const AppLive = Layer.empty.pipe(
Layer.provideMerge(FetchHttpClient.layer),
)
export const runtime = ReactRuntime.make(AppLive)
Provide The Runtime
At the React root, wrap your app with ReactRuntime.Provider:
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { ReactRuntime } from "effect-fc"
import { App } from "./App"
import { runtime } from "./runtime"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ReactRuntime.Provider runtime={runtime}>
<App />
</ReactRuntime.Provider>
</StrictMode>,
)
ReactRuntime.Provider also works with routers. Keep it above your router
provider so route components can be converted with the same runtime context.
Write Your First Component
Use Component.make when you want automatic tracing spans, or
Component.makeUntraced when you only want the component behavior.
import { Effect } from "effect"
import { Component } from "effect-fc"
import { runtime } from "./runtime"
const HelloEffect = Component.make("HelloEffect")(function* (props: {
readonly name: string
}) {
const message = yield* Effect.succeed(`Hello, ${props.name}`)
return <h1>{message}</h1>
})
export const Hello = HelloEffect.pipe(
Component.withRuntime(runtime.context),
)
Hello is now a regular React component:
import { Hello } from "./Hello"
export function App() {
return <Hello name="Effect" />
}
Use Services
Components can yield Effect services directly. Define services with Effect, provide them in your runtime layer, then consume them from the component body.
import { Effect } from "effect"
export class GreetingService extends Effect.Service<GreetingService>()(
"GreetingService",
{
succeed: {
greet: (name: string) => `Welcome, ${name}`,
},
},
) {}
Provide the service in your runtime:
import { Layer } from "effect"
import { ReactRuntime } from "effect-fc"
import { GreetingService } from "./services"
const AppLive = Layer.empty.pipe(
Layer.provideMerge(GreetingService.Default),
)
export const runtime = ReactRuntime.make(AppLive)
Then read it inside a component:
import { Component } from "effect-fc"
import { runtime } from "./runtime"
import { GreetingService } from "./services"
const GreetingEffect = Component.make("Greeting")(function* (props: {
readonly name: string
}) {
const greeting = yield* GreetingService
return <p>{greeting.greet(props.name)}</p>
})
export const Greeting = GreetingEffect.pipe(
Component.withRuntime(runtime.context),
)
Mount And Cleanup Effects
Use Component.useOnMount for scoped work that should start when the component
mounts and finalize when it unmounts.
import { Console, Effect } from "effect"
import { Component } from "effect-fc"
const Mounted = Component.make("Mounted")(function* () {
yield* Component.useOnMount(() =>
Effect.gen(function* () {
yield* Console.log("Mounted")
yield* Effect.addFinalizer(() => Console.log("Unmounted"))
}),
)
return <p>Open the console, then unmount me.</p>
})
Finalizers are tied to the component scope, so this is the right place for subscriptions, resources, and other lifecycle-bound Effects.
Where To Go Next
Once the runtime and component boundary are in place, the rest of the library builds on the same idea:
Subscribable.useAllreads Effect subscribables and rerenders when they change.Lensconnects React state and EffectSubscriptionRefvalues.QueryandMutationmodel async data and user-triggered operations.Form,SubmittableForm, andSynchronizedFormhelp build Effect-backed forms.
The important pattern is small and repeatable: write Effect-FC components inside
the runtime, then use Component.withRuntime at React boundaries.