@@ -0,0 +1,208 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: 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:
|
||||
|
||||
1. Install `effect-fc` with its peer dependencies.
|
||||
2. Create a React runtime from an Effect `Layer`.
|
||||
3. Wrap your React app with `ReactRuntime.Provider`.
|
||||
4. Write a component with `Component.make`.
|
||||
5. Convert it to a React component with `Component.withRuntime`.
|
||||
|
||||
## Install
|
||||
|
||||
Install `effect-fc` alongside `effect` and React 19.2 or newer:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install effect-fc effect react react-dom
|
||||
```
|
||||
|
||||
If your project uses TypeScript, also install React's type packages:
|
||||
|
||||
```bash npm2yarn
|
||||
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:
|
||||
|
||||
```tsx title="src/runtime.ts"
|
||||
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:
|
||||
|
||||
```tsx title="src/runtime.ts"
|
||||
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`:
|
||||
|
||||
```tsx title="src/main.tsx"
|
||||
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.
|
||||
|
||||
```tsx title="src/Hello.tsx"
|
||||
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:
|
||||
|
||||
```tsx title="src/App.tsx"
|
||||
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.
|
||||
|
||||
```ts title="src/services.ts"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export class GreetingService extends Effect.Service<GreetingService>()(
|
||||
"GreetingService",
|
||||
{
|
||||
succeed: {
|
||||
greet: (name: string) => `Welcome, ${name}`,
|
||||
},
|
||||
},
|
||||
) {}
|
||||
```
|
||||
|
||||
Provide the service in your runtime:
|
||||
|
||||
```tsx title="src/runtime.ts"
|
||||
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:
|
||||
|
||||
```tsx title="src/Greeting.tsx"
|
||||
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.
|
||||
|
||||
```tsx
|
||||
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.useAll` reads Effect subscribables and rerenders when they
|
||||
change.
|
||||
- `Lens` connects React state and Effect `SubscriptionRef` values.
|
||||
- `Query` and `Mutation` model async data and user-triggered operations.
|
||||
- `Form`, `SubmittableForm`, and `SynchronizedForm` help 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.
|
||||
Reference in New Issue
Block a user