Files
effect-view/packages/effect-fc-next/README.md
T
Julien Valverdé ffed4bda40 Improve README
2026-07-22 20:56:42 +02:00

156 lines
5.8 KiB
Markdown

<p align="center">
<a href="https://thila.dev/effect-view">
<img src="../docs/static/img/logo.svg" width="104" height="104" alt="Effect View logo" />
</a>
</p>
<h1 align="center">Effect View</h1>
<p align="center">
Write React components as typed Effect programs.
</p>
<p align="center">
<a href="https://www.npmjs.com/package/effect-view"><img src="https://img.shields.io/npm/v/effect-view?color=08777b" alt="npm version" /></a>
<a href="https://thila.dev/effect-view"><img src="https://img.shields.io/badge/docs-Effect_View-16a3a1" alt="Effect View documentation" /></a>
<a href="https://github.com/Thiladev/effect-view/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/effect-view?color=e99526" alt="MIT license" /></a>
</p>
<p align="center">
<strong><a href="https://thila.dev/effect-view">Read the documentation →</a></strong>
</p>
Effect View brings Effect's typed services, resource safety, concurrency, and
data modeling into React 19 without replacing React's component model. Yield
Effects and services from a component body, let scopes follow the React
lifecycle, and return ordinary JSX.
> **Effect v4 beta:** Effect View is built for the Effect v4 beta release. If
> your application uses Effect v3, use the legacy
> [`effect-fc` package](https://www.npmjs.com/package/effect-fc) instead.
```bash
npm install effect-view effect@beta react
```
Effect View does not depend on `react-dom`. Install the renderer used by your
application—for example, `react-dom` for the web:
```bash
npm install react-dom
```
You can use Effect View with React Native or any other React renderer instead.
## What it looks like
An Effect View component is an Effect program that produces JSX. It can create
scoped state, access services, and use React-facing hooks without leaving the
generator model:
```tsx
import { Effect, SubscriptionRef } from "effect"
import { Component, Lens } from "effect-view"
import { runtime } from "./runtime"
const CounterValueView = Component.make("CounterValue")(
function* ({ count }: { readonly count: Lens.Lens<number> }) {
yield* Component.useOnMount(() =>
Effect.addFinalizer(() =>
Effect.log("CounterValue unmounted"),
),
)
const [value, setValue] = yield* Lens.useState(count)
return (
<button onClick={() => setValue((n) => n + 1)}>
Count: {value}
</button>
)
},
)
const CounterView = Component.make("Counter")(function* () {
const count = yield* Component.useOnMount(() =>
Effect.map(
SubscriptionRef.make(0),
Lens.fromSubscriptionRef,
),
)
const CounterValue = yield* CounterValueView.use
return <CounterValue count={count} />
})
export const Counter = CounterView.pipe(
Component.withContext(runtime.context),
)
```
`CounterView` composes another Effect View component by yielding its `.use`
Effect, then renders the resulting component as ordinary JSX. The child can
access the current Effect context and receives its own component scope.
`Effect.addFinalizer` uses the scope already provided to the component body, so
the finalizer above runs when `CounterValue` unmounts.
At the outer boundary, `Counter` is still a normal React component and its Effect requirements remain
typed until they reach the runtime.
[Build the runtime and render your first component →](https://thila.dev/effect-view/docs/getting-started)
## Why Effect View
| | Capability | What it gives you |
| --- | --- | --- |
| **Effect components** | `Component.make` | Yield Effects and typed services directly, then return JSX. |
| **Managed lifecycles** | `Scope.Scope` | Finalizers, fibers, subscriptions, and resources close with their component or dependency lifecycle. |
| **One application runtime** | `ReactRuntime` | Build your Layer once and make its services available throughout the UI. |
| **Reactive state** | `Lens` and `View` | Focus large state models into small writable values and subscribe only where React needs them. |
| **Server state** | `Query` and `Mutation` | TanStack Query-style caching, invalidation, staleness, and mutations with typed Effects underneath. |
| **Schema-driven forms** | `MutationForm` and `LensForm` | Keep input-friendly encoded values while application code receives validated, decoded types. |
| **Async rendering** | `Async` and `Memoized` | Integrate asynchronous Effects with Suspense while retaining Effect errors and cancellation. |
## Designed for both ecosystems
Effect View does not hide Effect behind callbacks or recreate React around a
new rendering model. React still owns rendering, JSX, hooks, Suspense, and
events. Effect owns services, errors, resource safety, concurrency, streams,
and schemas. Effect View provides the lifecycle-aware boundary between them.
## Documentation
- [Getting started](https://thila.dev/effect-view/docs/getting-started)
- [State management](https://thila.dev/effect-view/docs/state-management)
- [Queries](https://thila.dev/effect-view/docs/query)
- [Mutations](https://thila.dev/effect-view/docs/mutation)
- [Forms](https://thila.dev/effect-view/docs/forms)
The complete documentation is available at
**[thila.dev/effect-view](https://thila.dev/effect-view)**.
## Requirements
- React 19.2 or newer
- **Effect v4 beta** (`effect@beta`)
- TypeScript and `@types/react` for TypeScript projects
Effect View is renderer-independent and does not require `react-dom`.
## Project status
Effect View is currently beta software. The main APIs are available, but
breaking changes and rough edges are still possible before a stable release.
React Fast Refresh does not yet recognize Effect View component definitions, so
changes to them currently require a page reload; ordinary React components are
unaffected (currently being worked on).
Issues, ideas, and contributions are welcome on
[GitHub](https://github.com/Thiladev/effect-view).
## License
[MIT](https://github.com/Thiladev/effect-view/blob/main/LICENSE)