Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tim-smart/effect-atom/llms.txt

Use this file to discover all available pages before exploring further.

The @effect-atom/atom-vue package provides Vue composables for integrating Effect Atom into your Vue 3 applications.

Installation

pnpm add @effect-atom/atom-vue

Core composables

useAtomValue

Read the current value of an atom and subscribe to changes.
<script setup lang="ts">
import { Atom, useAtomValue } from "@effect-atom/atom-vue"

const countAtom = Atom.make(0)
const count = useAtomValue(() => countAtom)
</script>

<template>
  <h1>{{ count }}</h1>
</template>
useAtomValue returns a Vue Ref that automatically updates when the atom value changes.

useAtomSet

Get a setter function for an atom without subscribing to its value.
<script setup lang="ts">
import { Atom, useAtomSet } from "@effect-atom/atom-vue"

const countAtom = Atom.make(0)
const setCount = useAtomSet(() => countAtom)

const increment = () => setCount((count) => count + 1)
</script>

<template>
  <button @click="increment">Increment</button>
</template>

Mode options

For atoms that return Result types, you can specify how to handle the result:
<script setup lang="ts">
const setCount = useAtomSet(() => effectfulAtom)

const handleClick = () => {
  setCount(42) // Returns void immediately
}
</script>

useAtom

Combines useAtomValue and useAtomSet for reading and writing in a single composable.
<script setup lang="ts">
import { Atom, useAtom } from "@effect-atom/atom-vue"

const countAtom = Atom.make(0)
const [count, setCount] = useAtom(() => countAtom)

const increment = () => setCount((c) => c + 1)
const decrement = () => setCount((c) => c - 1)
</script>

<template>
  <div>
    <h1>{{ count }}</h1>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

useAtomRef

Subscribe to an AtomRef value.
<script setup lang="ts">
import { AtomRef, useAtomRef } from "@effect-atom/atom-vue"

const formRef = AtomRef.make({ name: "", email: "" })
const form = useAtomRef(() => formRef)
</script>

<template>
  <div>{{ form.name }} - {{ form.email }}</div>
</template>

Registry management

By default, Vue uses a global registry for all atoms. You can create isolated registries using the registry injection system.

injectRegistry

Access the current registry from the component context.
<script setup lang="ts">
import { injectRegistry } from "@effect-atom/atom-vue"

const registry = injectRegistry()
// Use registry directly for advanced operations
</script>

Provide custom registry

Provide a custom registry to a component tree using Vue’s provide/inject.
<script setup lang="ts">
import { Registry, registryKey } from "@effect-atom/atom-vue"
import { provide } from "vue"

const customRegistry = Registry.make({
  defaultIdleTTL: 1000
})

provide(registryKey, customRegistry)
</script>

<template>
  <div>
    <!-- Child components will use customRegistry -->
    <Counter />
  </div>
</template>
Each custom registry creates an isolated atom state scope. Atoms are not shared between different registries.

Working with effects

Vue composables work seamlessly with Effect atoms.
<script setup lang="ts">
import { Atom, Result, useAtomValue } from "@effect-atom/atom-vue"
import { Effect } from "effect"
import { computed } from "vue"

interface User {
  id: string
  name: string
}

const userAtom = Atom.make(
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise(() =>
      fetch("/api/user").then(r => r.json())
    )
    return response as User
  })
)

const result = useAtomValue(() => userAtom)
const user = computed(() => Result.getOrElse(result.value, () => null))
const isLoading = computed(() => result.value._tag === "Initial")
</script>

<template>
  <div v-if="isLoading">Loading...</div>
  <div v-else-if="user">
    <h2>{{ user.name }}</h2>
    <p>ID: {{ user.id }}</p>
  </div>
</template>

Working with streams

Effect Atom works seamlessly with Effect streams for reactive data sources.
<script setup lang="ts">
import { Atom, Result, useAtomValue } from "@effect-atom/atom-vue"
import { Schedule, Stream, Cause } from "effect"
import { computed } from "vue"

const countAtom = Atom.make(Stream.fromSchedule(Schedule.spaced(1000)))
const result = useAtomValue(() => countAtom)

const count = computed(() => 
  Result.getOrElse(result.value, () => 0)
)

const error = computed(() =>
  result.value._tag === "Failure" 
    ? Cause.pretty(result.value.cause) 
    : null
)
</script>

<template>
  <div v-if="error">Error: {{ error }}</div>
  <div v-else>Count: {{ count }}</div>
</template>

Pull-based streams

For paginated or infinite scroll data:
<script setup lang="ts">
import { Atom, Result, useAtom } from "@effect-atom/atom-vue"
import { Stream } from "effect"
import { computed } from "vue"

const itemsAtom = Atom.pull(Stream.range(1, 100))
const [result, loadMore] = useAtom(() => itemsAtom)

const items = computed(() =>
  result.value._tag === "Success" ? result.value.value.items : []
)

const done = computed(() =>
  result.value._tag === "Success" ? result.value.value.done : false
)
</script>

<template>
  <div>
    <ul>
      <li v-for="item in items" :key="item">{{ item }}</li>
    </ul>
    <button v-if="!done" @click="() => loadMore()">Load more</button>
  </div>
</template>

Complete example

<script setup lang="ts">
import { Atom, useAtomValue, useAtomSet } from "@effect-atom/atom-vue"

const countAtom = Atom.make(0).pipe(Atom.keepAlive)

const count = useAtomValue(() => countAtom)
const setCount = useAtomSet(() => countAtom)

const increment = () => setCount((c) => c + 1)
const decrement = () => setCount((c) => c - 1)
</script>

<template>
  <div>
    <h1>{{ count }}</h1>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

Reactive atom parameters

All composables accept a function that returns the atom. This allows you to use reactive values when selecting atoms.
<script setup lang="ts">
import { Atom, useAtomValue } from "@effect-atom/atom-vue"
import { ref, computed } from "vue"

const userIdAtom = Atom.family((id: string) =>
  Atom.make(
    Effect.gen(function* () {
      const response = yield* Effect.tryPromise(() =>
        fetch(`/api/users/${id}`).then(r => r.json())
      )
      return response
    })
  )
)

const selectedId = ref("1")

// Atom reactively updates when selectedId changes
const user = useAtomValue(() => userIdAtom(selectedId.value))
</script>

<template>
  <div>
    <select v-model="selectedId">
      <option value="1">User 1</option>
      <option value="2">User 2</option>
      <option value="3">User 3</option>
    </select>
    <div>{{ user }}</div>
  </div>
</template>

Build docs developers (and LLMs) love