Upgrade to Effect 4 final (#79)
Lint / lint (push) Successful in 25s
Publish / publish (push) Successful in 2m2s

## Summary

Upgrades Effect View to the stable **Effect 4** release and the matching `effect-lens@2.0.3`.

- `effect` and `@effect/platform-browser` → `4.0.0`; `effect-lens` → `2.0.3` (peer `effect ^4.0.0`)
- effect-view's `effect` peer switched from the exact RC pin to `^4.0.0`
- Migrated the stabilized import paths:
  - `effect/unstable/reactivity` → `effect/reactivity`
  - `effect/unstable/http` → `effect/http`
- Docs/README: replaced RC wording and install tags (`effect@rc` / `effect@beta`) with stable `effect@^4`
- Example app relies on caret ranges for the Effect packages

## Verification

- `bun run lint:tsc` ✅ (9/9 tasks)
- `bun run test` ✅ (effect-view 33, effect-fc 20, vite-plugin 15)
- `bun run build` ✅ (5/5 packages, docs included)
- `bun run lint:biome` ✅ (6/6)

Legacy `effect-fc` (Effect v3) is intentionally left untouched.

---------

Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Reviewed-on: #79
This commit was merged in pull request #79.
This commit is contained in:
ThilawynandJulien Valverdé committed 2026-10-04 18:10:25 +02:00
1 parent 7f2782676b
commit 1cc2767308
19 files changed
+44 -43

No files matched your search

+12 -12
View File
@@ -82,21 +82,21 @@
},
"packages/effect-view": {
"name": "effect-view",
"version": "0.1.5",
"version": "0.1.7",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"effect-lens": "2.0.2-rc.115",
"effect-lens": "2.0.3",
},
"devDependencies": {
"@effect/platform-browser": "4.0.0-rc.115",
"@effect/platform-browser": "^4.0.0",
"@testing-library/react": "^16.3.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"jsdom": "^26.1.0",
"vitest": "^3.2.4",
},
"peerDependencies": {
"@types/react": "^19.2.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"react": "^19.2.0",
},
},
@@ -104,9 +104,9 @@
"name": "@effect-view/example",
"version": "0.0.0",
"dependencies": {
"@effect/platform-browser": "4.0.0-rc.115",
"@effect/platform-browser": "^4.0.0",
"@radix-ui/themes": "^3.3.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"effect-view": "workspace:*",
"react-icons": "^5.6.0",
},
@@ -3060,9 +3060,9 @@
"@docusaurus/utils/jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
"@effect-view/example/@effect/platform-browser": ["@effect/platform-browser@4.0.0-rc.115", "", { "peerDependencies": { "effect": "^4.0.0-rc.115" } }, "sha512-RwpnxRYSBHBL1HYYlRwIrwz0+1hwmn5RvgqkKI0Tb+ChOIR9UCMpXsAhnwQcGP+mBidrC7dlloL42poGHZW4yw=="],
"@effect-view/example/@effect/platform-browser": ["@effect/platform-browser@4.0.0", "", { "peerDependencies": { "effect": "^4.0.0" } }, "sha512-OHo7EPpF3RVPiI+mjoU9yd05LdCdKPjzDXqs5wRgF9CsdVvwN76B0zgt54DxPUcvMYri1tVJOtLCVnK0tRkNQQ=="],
"@effect-view/example/effect": ["effect@4.0.0-rc.115", "", {}, "sha512-ogYulZ5ffeOzrJqQrG0XOkDO5lKn2s9KNHhoxJy/6wKUkF0i12dlVXWA9TgsgQ+zV/JzizyG0+XMPepZEQX6vw=="],
"@effect-view/example/effect": ["effect@4.0.0", "", {}, "sha512-ooc1TG5t+FfzgYnFz2ff6BBKyZ7EwBRVXC7c4RhQUAD6/TZ2gTXXMeb4WX7a19ozQo4J73/QW+S00YAIresoMQ=="],
"@effect-view/vite-plugin/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
@@ -3130,11 +3130,11 @@
"dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"effect-view/@effect/platform-browser": ["@effect/platform-browser@4.0.0-rc.115", "", { "peerDependencies": { "effect": "^4.0.0-rc.115" } }, "sha512-RwpnxRYSBHBL1HYYlRwIrwz0+1hwmn5RvgqkKI0Tb+ChOIR9UCMpXsAhnwQcGP+mBidrC7dlloL42poGHZW4yw=="],
"effect-view/@effect/platform-browser": ["@effect/platform-browser@4.0.0", "", { "peerDependencies": { "effect": "^4.0.0" } }, "sha512-OHo7EPpF3RVPiI+mjoU9yd05LdCdKPjzDXqs5wRgF9CsdVvwN76B0zgt54DxPUcvMYri1tVJOtLCVnK0tRkNQQ=="],
"effect-view/effect": ["effect@4.0.0-rc.115", "", {}, "sha512-ogYulZ5ffeOzrJqQrG0XOkDO5lKn2s9KNHhoxJy/6wKUkF0i12dlVXWA9TgsgQ+zV/JzizyG0+XMPepZEQX6vw=="],
"effect-view/effect": ["effect@4.0.0", "", {}, "sha512-ooc1TG5t+FfzgYnFz2ff6BBKyZ7EwBRVXC7c4RhQUAD6/TZ2gTXXMeb4WX7a19ozQo4J73/QW+S00YAIresoMQ=="],
"effect-view/effect-lens": ["effect-lens@2.0.2-rc.115", "", { "peerDependencies": { "effect": "4.0.0-rc.115" } }, "sha512-7Vmtcce7HvAuIiSkQN1mdlFLXohY2B858CS8toEXzx0fCCsRBoCi/jCM0J/D00Q0j9OLP7QBMGv15hjVkppbkg=="],
"effect-view/effect-lens": ["effect-lens@2.0.3", "", { "peerDependencies": { "effect": "^4.0.0" } }, "sha512-FLILgEXUf1aKxykcVR+Rc3qmWYPSeb4zLc2NdNEW+RNgnsICTan8rhhXW374e0dwEQAsEkMmcHxeEaQxBqy0jA=="],
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+2 -2
View File
@@ -24,7 +24,7 @@ For a web application, install Effect View with Effect 4 and React 19.2 or
newer:
```bash npm2yarn
npm install effect-view effect@beta react react-dom
npm install effect-view effect react react-dom
```
```bash npm2yarn
@@ -69,7 +69,7 @@ layers needed by the UI:
```tsx title="src/runtime.ts"
import { Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { FetchHttpClient } from "effect/http"
import { ReactRuntime } from "effect-view"
const AppLive = Layer.empty.pipe(
+1 -1
View File
@@ -37,7 +37,7 @@ create it in an Effect service when it should be shared.
```tsx
import { Effect } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
import { Component, Mutation, View } from "effect-view"
import { sendInvite } from "./api"
+3 -3
View File
@@ -68,7 +68,7 @@ than reconstructing the query during render.
```tsx
import { Effect, Schema, SubscriptionRef } from "effect"
import { HttpClient } from "effect/unstable/http"
import { HttpClient } from "effect/http"
import { Component, Lens, Query, View } from "effect-view"
const Post = Schema.Struct({
@@ -142,7 +142,7 @@ interface QueryState<K, A, E> {
Subscribe with `View.useAll`, then match the result explicitly:
```tsx
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
const [state] = yield* View.useAll([query.state])
@@ -303,7 +303,7 @@ Window-focus refresh depends on the optional `@effect/platform-browser`
integration. Install it in browser applications that use this behavior:
```bash npm2yarn
npm install @effect/platform-browser@beta
npm install @effect/platform-browser
```
When the package is available, focusing the window resolves the current key
+1 -1
View File
@@ -121,7 +121,7 @@ export default function Home(): ReactNode {
<span className={styles.installPrompt} aria-hidden="true">
$
</span>
<code>npm install effect-view effect@rc</code>
<code>npm install effect-view effect</code>
</div>
<div className={styles.actions}>
+1 -1
View File
@@ -2,7 +2,7 @@
`effect-view` lets a React function component be described as an Effect program: yield services, create scoped resources, subscribe to reactive state, and turn Effects into React callbacks inside a component body, then convert that description into a normal React function component at a React boundary.
Requires Effect v4 (RC) and React 19.2+. Peer dependencies: `effect`, `react`, `@types/react`. Not tied to `react-dom` — any React renderer works.
Requires Effect v4 and React 19.2+. Peer dependencies: `effect`, `react`, `@types/react`. Not tied to `react-dom` — any React renderer works.
When writing effect-view code, use the actual current source and tests in `src/` as ground truth over anything remembered from training — the API is pre-1.0 and still moving. The files below are a concise reference; read the linked one(s) before writing code that touches that concern.
+3 -3
View File
@@ -25,12 +25,12 @@ 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 RC:** Effect View is built for the Effect v4 release candidate. If
> **Effect v4:** Effect View is built for Effect v4. 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@rc react
npm install effect-view effect react
```
Effect View does not depend on `react-dom`. Install the renderer used by your
@@ -127,7 +127,7 @@ The complete documentation is available at **[thila.dev/effect-view](https://thi
## Requirements
- React 19.2 or newer
- **Effect v4 RC** (`effect@rc`)
- Effect 4 or newer
- TypeScript and `@types/react` for TypeScript projects
Effect View is renderer-independent and does not require `react-dom`.
+1 -1
View File
@@ -28,7 +28,7 @@ const mutation = yield* Component.useOnMount(() =>
`mutation.state` is a `View` of `{ key: Option<K>, result: AsyncResult<A, E> }`. `result` starts `Initial` (`waiting: false`); calling `mutate`/`mutateView` sets `waiting: true`, then publishes `Success` or `Failure`. Match on `state.result`, not `state` itself:
```tsx
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
const [state] = yield* View.useAll([mutation.state])
+2 -2
View File
@@ -36,7 +36,7 @@ export const runtime = ReactRuntime.make(AppLive)
```tsx
import { Effect, Schema, SubscriptionRef } from "effect"
import { HttpClient } from "effect/unstable/http"
import { HttpClient } from "effect/http"
import { Component, Lens, Query, View } from "effect-view"
const [postId, query] = yield* Component.useOnMount(() =>
@@ -70,7 +70,7 @@ const [postId, query] = yield* Component.useOnMount(() =>
`query.state: View<QueryState<K, A, E>>` where `QueryState = { key: K; result: AsyncResult<A, E> }`.
```tsx
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
const [state] = yield* View.useAll([query.state])
+6 -5
View File
@@ -1,12 +1,13 @@
{
"name": "effect-view",
"description": "Write React function components with Effect",
"version": "0.1.7",
"version": "0.2.0",
"type": "module",
"files": [
"./README.md",
"./AGENTS.md",
"./src/**/*.ts",
"!./src/**/*.test.ts",
"./dist/**/*.js",
"./dist/**/*.js.map",
"./dist/**/*.d.ts",
@@ -102,19 +103,19 @@
"clean:modules": "rm -rf node_modules"
},
"devDependencies": {
"@effect/platform-browser": "4.0.0-rc.115",
"@effect/platform-browser": "^4.0.0",
"@testing-library/react": "^16.3.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"jsdom": "^26.1.0",
"vitest": "^3.2.4"
},
"peerDependencies": {
"@types/react": "^19.2.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"react": "^19.2.0"
},
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"effect-lens": "2.0.2-rc.115"
"effect-lens": "2.0.3"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Cause, Deferred, Effect, Option, type Scope } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
import { describe, expect, it } from "vitest"
import * as Mutation from "./Mutation.js"
import * as View from "./View.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Cause, type Context, Effect, Exit, type Fiber, Option, Pipeable, Predicate, PubSub, Ref, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
import * as Lens from "./Lens.js"
import * as View from "./View.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import type { StandardSchemaV1 } from "@standard-schema/spec"
import { Array, Cause, type Context, Effect, Fiber, Option, Pipeable, Predicate, Schema, SchemaIssue, type Scope, Semaphore, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
import * as Form from "./Form.js"
import * as Lens from "./Lens.js"
import * as Mutation from "./Mutation.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Effect, Schedule, type Scope, Stream } from "effect"
import { AsyncResult } from "effect/reactivity"
import { TestClock } from "effect/testing"
import { AsyncResult } from "effect/unstable/reactivity"
import { describe, expect, it } from "vitest"
import * as Query from "./Query.js"
import * as QueryClient from "./QueryClient.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Cause, type Context, Duration, Effect, Equal, type Equivalence, Exit, Fiber, Function, Option, Pipeable, Predicate, PubSub, Ref, type Schedule, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { AsyncResult } from "effect/reactivity"
import * as Lens from "./Lens.js"
import * as QueryClient from "./QueryClient.js"
import * as View from "./View.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { type Cause, Context, DateTime, Duration, Effect, Equal, Equivalence, Hash, HashMap, Layer, type Option, Pipeable, Predicate, Schedule, type Scope, Semaphore, SubscriptionRef } from "effect"
import type { AsyncResult } from "effect/unstable/reactivity"
import type { AsyncResult } from "effect/reactivity"
import * as Lens from "./Lens.js"
import type * as View from "./View.js"
+3 -3
View File
@@ -27,15 +27,15 @@
"vite": "^8.0.16"
},
"dependencies": {
"@effect/platform-browser": "4.0.0-rc.115",
"@effect/platform-browser": "^4.0.0",
"@radix-ui/themes": "^3.3.0",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"effect-view": "workspace:*",
"react-icons": "^5.6.0"
},
"overrides": {
"@types/react": "^19.2.15",
"effect": "4.0.0-rc.115",
"effect": "^4.0.0",
"react": "^19.2.6"
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Effect, Schema, SubscriptionRef } from "effect"
import { HttpClient } from "effect/unstable/http"
import { AsyncResult } from "effect/unstable/reactivity"
import { HttpClient } from "effect/http"
import { AsyncResult } from "effect/reactivity"
import { Component, Lens, Mutation, Query, View } from "effect-view"
import { runtime } from "@/runtime"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser"
import { DateTime, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { FetchHttpClient } from "effect/http"
import { QueryClient, ReactRuntime } from "effect-view"