Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 158 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,182 @@
[![size](https://img.shields.io/bundlephobia/minzip/valtio-reactive)](https://bundlephobia.com/result?p=valtio-reactive)
[![discord](https://img.shields.io/discord/627656437971288081)](https://discord.gg/MrQdmzd)

valtio-reactive makes Valtio a reactive library
Reactive primitives for [valtio](https://github.com/pmndrs/valtio) — adds, `computed`, `effect`, and `batch` to enable fine-grained reactivity outside of React.

## Background
## Motivation

See: https://github.com/pmndrs/valtio/discussions/949
`valtio`'s reactive capabilities are primarily designed for React via `useSnapshot` and only has limited support for computed values. `valtio-reactive` was made to fill those gaps while keeping `valtio` lean and fast.

## Install
- Run side effects when specific properties change (not just any change)
- Create derived/computed state that automatically updates
- Batch multiple updates into a single reaction

See the [original discussion](https://github.com/pmndrs/valtio/discussions/949) for more context.

## Installation

```bash
npm install valtio valtio-reactive
```

## Usage
## API

### `effect(fn, cleanup?): Dispose

This runs the first function (`fn`) immediately and re-runs it whenever any of the properties that are accessed in that function change. Only the properties are actually read during execution are tracked — changes to unread properties won't trigger re-runs. It returns a `dispose` function that will run the cleanup function when called.

```ts
import { proxy } from 'valtio/vanilla';
import { effect } from 'valtio-reactive';

const state = proxy({
count: 0,
unrelated: 'hello'
user: {
settings: {
theme: 'light' //
},
name: 'Bob'
},

})

const dispose = effect(
() => {
console.log('count is: ', state.count)
console.log('theme is: ', state.user.settings.theme)
},
() => {
// optional cleanup function
console.log('cleaning up')
}
)
// immediately logs:
// "count is: 0"
// "theme is: light'
state.count++
// logs:
// "count is: 1"
// "theme is: light"
state.unrelated = 'world' // nothing happens when this property is changed because it wasn't accessed
state.user.name = 'Robert' // nothing happens

state.user.settings.theme = 'dark'
// logs:
// "count is: 1"
// "theme is: dark"

dispose()
// logs "cleaning up"
```

---

### `batch(fn): T`

Batches multiple state changes so that effects only react once after all changes complete. Returns the value returned by `fn`.

```ts
import { proxy } from 'valtio/vanilla';
import { batch, effect } from 'valtio-reactive';

const state = proxy({ count: 0 });

effect(() => {
console.log('count:', state.count);
});
// Logs: "count: 0"

batch(() => {
state.count++;
state.count++;
state.count++;
});
// Logs: "count: 3" (only once, not three times)
```

---

### `computed(obj): T`

Creates a proxy object with computed/derived properties. Each property is defined as a getter function that automatically re-runs when its dependencies change.

```js
```ts
import { proxy } from 'valtio/vanilla';
import { batch, computed, effect } from 'valtio-reactive';
import { computed } from 'valtio-reactive';

const state = proxy({ count: 1 });

const derived = computed({
double: () => state.count * 2,
quadruple: () => state.count * 4,
});

console.log(derived.double); // 2
console.log(derived.quadruple); // 4

state.count = 5;

console.log(derived.double); // 10
console.log(derived.quadruple); // 20
```

The returned object is itself a `valtio` proxy, so you can use it with `effect`, `useSnapshot`, or any other `valtio` utility.

---

## Usage with React

While these primitives are framework-agnostic, they integrate seamlessly with `valtio`'s React bindings:

```tsx
import { proxy, useSnapshot } from 'valtio';
import { effect, computed } from 'valtio-reactive';

const state = proxy({ count: 0 });

// Computed values work with useSnapshot
const derived = computed({
double: () => state.count * 2,
});

// Side effects outside of React
effect(() => {
console.log('double count:', derived.double);
console.log('Count changed:', state.count);
});

setInterval(() => {
batch(() => {
state.count++;
state.count++;
});
}, 1000);
function Counter() {
const snap = useSnapshot(state);
const derivedSnap = useSnapshot(derived);

return (
<div>
<p>Count: {snap.count}</p>
<p>Double: {derivedSnap.double}</p>
<button onClick={() => state.count++}>+1</button>
</div>
);
}
```

---

## TypeScript

All exports are fully typed. The `computed` function infers types from your getter functions:

```ts
const state = proxy({ count: 1, name: 'test' });

const derived = computed({
double: () => state.count * 2, // inferred as number
message: () => `Hello ${state.name}`, // inferred as string
});

derived.double; // number
derived.message; // string
```

## License

MIT