Skip to content

Releases: jotaijs/jotai-effect

v2.0.0

18 Feb 05:06
9f4ac62
Compare
Choose a tag to compare

We’re excited to announce the release of jotai-effect v2, which brings a single but significant change to the core API: atomEffect now runs synchronously whenever it mounts or its dependencies change. This update improves consistency, helps avoid race conditions, and keeps related state changes in sync.


What’s New?

Synchronous atomEffect

  • In v1, atomEffect would run asynchronously in the next microtask.
  • In v2, atomEffect runs synchronously on mount and whenever the dependencies it uses have changed.
  • Batching is still supported when you update multiple dependencies in a single writable atom. The effect runs only after that writable atom has finished all its updates, preventing partial updates or intermediate states.

Example:

const syncEffect = atomEffect((get, set) => {
  get(someAtom)
  set(anotherAtom)
})

const store = createStore()
store.set(someAtom, (v) => v + 1)
// The effect above runs immediately, so anotherAtom is updated in the same microtask
console.log(store.get(anotherAtom)) // Updated by atomEffect synchronously

When someAtom is updated, the effect runs immediately, updating anotherAtom in the same turn. If you update multiple atoms in the same writable atom, these changes are batched together, and atomEffect runs after those updates complete.


Migration Guide

For most users, no change is required. If you depended on the old microtask delay or cross-atom batching, read on.

1. Adding back the microtask delay

If your logic explicitly relied on atomEffect running in a separate microtask, you can reintroduce the delay yourself:

Before (v1)

const effect = atomEffect((get, set) => {
  console.log('effect')
  return () => {
    console.log('cleanup')
  }
})

After (v2)

const effect = atomEffect((get, set) => {
  queueMicrotask(() => {
    console.log('effect')
  })
  return () => {
    queueMicrotask(() => {
      console.log('cleanup')
    })
  }
})

2. Batching updates

In v1, updates to separate atoms were implicitly batched in the next microtask. In v2, batching only occurs within a single writable atom update:

Before (v1)

store.set(atomA, (v) => v + 1)
store.set(atomB, (v) => v + 1)
// atomEffect would 'see' both changes together in the next microtask

After (v2)

const actionAtom = atom(null, (get, set) => {
  set(atomA, (v) => v + 1)
  set(atomB, (v) => v + 1)
})

store.set(actionAtom)
// atomEffect now runs after both updates, in one batch

A Special Thanks to Daishi Kato

I’d like to extend my deepest gratitude to Daishi Kato, author of Jotai. Daishi dedicated months of tireless work to rework and rewrite significant parts of the Jotai core—primarily to empower community library authors such as myself to implement features such as synchronous effects in jotai-effect. His willingness to refine Jotai’s internals and his thoughtfulness in API design made this effort possible. Thank you.

Final Thoughts

  • Most code will just work without any changes.
  • If you have specialized scenarios relying on microtask delays or separate updates to multiple atoms, you’ll need to wrap them in a single writable atom or manually queue the microtask.

We hope these improvements make your state management more predictable and easier to reason about. If you have any issues, please feel free to open a GitHub Discussion. Happy coding!

v1.1.4

26 Jan 01:22
Compare
Choose a tag to compare

🚀 Highlights

1. New observe API for Managing Side-Effects

PR: #55

This release introduces the observe API, a lightweight and flexible utility for managing global side effects in Jotai. observe allows you to subscribe to state changes on a Jotai store and execute reactive logic without being tied to React's lifecycle.

Example Usage

import { observe } from 'jotai-effect'

observe((get, set) => {
  set(logAtom, `someAtom changed: ${get(someAtom)}`)
})

Usage With React

When using a Jotai Provider, pass the store to both observe and the Provider to ensure the effect is mounted on the correct store.

const store = createStore()

observe((get, set) => {
  set(logAtom, `someAtom changed: ${get(someAtom)}`)
}, store)

<Provider store={store}>...</Provider>

observe makes managing Jotai state-dependent logic simpler and more ergonomic. Check out the documentation for more details.


2. Repository Modernization

PR: #57

This release also modernizes the repository to align with the latest development standards:

  • Switched to Vite + Vitest for faster builds and modern testing.
  • Updated TypeScript and ESLint Configurations to leverage modern tooling.
  • Improved Repository Structure by renaming __tests__ to tests and removing outdated examples.
  • Enhanced README Clarity to simplify onboarding and usage.

📦 Installation

To update to v1.1.4, run:

npm install [email protected]

We hope you enjoy these updates! Feedback and contributions are always welcome. Open an issue if you encounter any problems.

v1.0.7

22 Dec 06:57
Compare
Choose a tag to compare

v1.0.6

17 Dec 22:40
Compare
Choose a tag to compare

v1.0.5

04 Dec 23:27
Compare
Choose a tag to compare

What's Changed

  • atomEffect should not suspend the component by @dmaskasky in #45

Full Changelog: v1.0.4...v1.0.5

v1.0.2

24 Aug 23:04
458b14e
Compare
Choose a tag to compare
  • Addresses a compatibility issue with [email protected] that caused atomEffects to lose their dependencies during recomputations.
  • Introduces a new utility helper withAtomEffect

Usage

const anAtom = atom(0)

// the effect is automatically added to the resulting atom
// setting the resulting atom is forwarded to the atom passed in
const anAtomWithEffect = withAtomEffect(anAtom, (get, set) => {
  console.log(`anAtom value is now ${get(anAtom)}`)
})
...
const [value, setValue] = useAtom(anAtomWithEffect)

v1.0.0

06 Apr 22:38
Compare
Choose a tag to compare

I am thrilled to announce the release of Jotai Effect version 1.0! This milestone represents a significant achievement marking its readiness for production use.

Jotai Effect was started last October with the aim of providing a utility package for reactive side effects within the Jotai ecosystem. Over the past six months, I have been closely monitoring the API's stability and its effectiveness in real-world applications.

For those eagerly waiting for Jotai to be considered production-ready, the moment has finally arrived. The feedback from our community has been overwhelmingly positive, and today, I'm confident that Jotai Effect is ready for its prime time.

I couldn't have reached this point without the help of our amazing contributors, Daishi Kato (@dai-shi) and Alex Yang (@himself65). Their early contributions were pivotal in honing the library to what it is today. Additionally, I want to extend my gratitude to our vibrant community on Discord for their continuous support and feedback.
Thank you.

As we celebrate this release, I encourage you to explore Jotai Effect and discover how it can streamline your reactive programming workflows. Happy Coding!

Best regards,
David Maskasky

The full discussion can be viewed here.

v0.6.0

29 Feb 11:00
Compare
Choose a tag to compare

What's Changed

  • rethrow errors thrown during effectFn and cleanup by @dmaskasky in #33

Full Changelog: v0.5.0...v0.6.0

v0.5.0

01 Feb 21:37
Compare
Choose a tag to compare

Adds support for reading an atom's value without subscribing to it.

Read atom data without subscribing to changes with get.peek.

const countAtom = atom(0)
atomEffect((get, set) => {
  // will not rerun when countAtom changes
  const count = get.peek(countAtom)
})

What's Changed

  • feat: get.peek reads atom value without subscribing to updates by @dmaskasky in #30

Full Changelog: v0.4.0...v0.5.0

v0.4.0

25 Jan 01:23
Compare
Choose a tag to compare

Adds support for recursion. 🥳

Recursion is supported with set.recurse for both sync and async use cases, but is not supported in the cleanup function.

const countAtom = atom(0)
atomEffect((get, set) => {
  // increments count once per second
  const count = get(countAtom)
  const timeoutId = setTimeout(() => {
    set.recurse(countAtom, increment)
  }, 1000)
  return () => clearTimeout(timeoutId)
})

What's Changed

Full Changelog: v0.3.2...v0.4.0