How to Organize Actions
Valtio is unopinionated about how you structure your state mutation logic. Here are several common patterns for organizing actions.
1. Actions as Separate Exported Functions (Recommended)
This pattern keeps state and actions separate, which is excellent for code-splitting and maintainability.
// store.js
import { proxy } from 'valtio';
export const state = proxy({
count: 0,
name: 'foo',
});
export const inc = () => {
++state.count;
};
export const setName = (name) => {
state.name = name;
};
2. Actions as a Separate Exported Object
This pattern groups all actions into a single object, which can be convenient for importing.
// store.js
import { proxy } from 'valtio';
export const state = proxy({
count: 0,
name: 'foo',
});
export const actions = {
inc: () => {
++state.count;
},
setName: (name) => {
state.name = name;
},
};
3. Action Methods Defined in State
You can define actions as methods directly on the state object. Arrow functions are recommended to avoid issues with this
context.
// store.js
import { proxy } from 'valtio';
export const state = proxy({
count: 0,
name: 'foo',
inc: () => {
++state.count; // References the exported `state` directly
},
setName: (name) => {
state.name = name;
},
});
4. Action Methods Using this
(Advanced)
Using this
is possible but can be tricky. When calling state.inc()
, this
correctly refers to the proxy. However, if you call snap.inc()
on a snapshot, this
will refer to the immutable snapshot, and the mutation will fail.
// store.js
import { proxy } from 'valtio';
export const state = proxy({
count: 0,
name: 'foo',
inc() {
++this.count;
},
setName(name) {
this.name = name;
},
});
5. Using a Class
For those who prefer an object-oriented approach, you can define your state and actions within a class and then wrap an instance of it with proxy
.
// store.js
import { proxy } from 'valtio';
class Store {
count = 0;
name = 'foo';
inc() {
++this.count;
}
setName(name) {
this.name = name;
}
}
export const state = proxy(new Store());