Component stores
Access and manipulate the state of Ariakit components in a performant way through component stores.
Overview
Component stores are lower-level React hooks exported by the @ariakit/react
package. They allow you to read and write the state of Ariakit components. With them, you can control the value of a Combobox, the active item of a Menu, the open state of a Dialog, or the values in a Form, for example.
You instantiate the store by calling the component store hook within a React component:
function MyCombobox() {
}
Then, you should pass the store to the wrapping components of that module:
Passing the store to the child components, such as ComboboxItem
, is optional.
Providing state to the store
Component stores accept an optional object as an argument. This object is used to initialize and control the state of the component.
Default state
Conventionally, when dealing with dynamic state, the initial value passed to the store has its property name prefixed with the word default
. In this case, only the initial value will be considered. It doesn't have to be referentially stable between re-renders.
});
State setters
Component stores may also accept callbacks for state changes. These functions conventionally bear the name of the state property they modify, prefixed with the word set
. They are invoked with the new state whenever an update occurs.
These state setters serve various purposes, such as updating another state, executing side effects, or implementing features like onChange
, onValuesChange
, onToggle
, onOpenChange
, and so on.
console.log(values);
},
});
Controlled state
You can take full control of the state by passing the exact property, without prefixes, to the store. In this case, the state will be considered controlled and the component will not update the state internally. It will only call the state setter. You can use this to implement a controlled component using React.useState
:
const [values, setValues] = React.useState({ name: "", email: "" });
You can also receive controlled props, such as value
and onChange
, from a parent component and pass them directly to the store:
});
Reading the state
Ariakit exposes a generic useStoreState
hook that you can use to read the state in a performant way.
Watching the entire state
Calling useStoreState
without a second argument returns the entire state object and re-renders the component whenever the state changes.
function MyCombobox() {
console.log(state.value);
}
Watching a specific state property
Alternatively, you can pass a string to useStoreState
to read the value of a specific state property. The component will only re-render when the requested value changes.
Computed values
Finally, useStoreState
accepts a selector function as the second argument. The function will receive the state as a parameter and should return a value, which can be computed inside the function body.
You're free to use other variables within this function. The selector will be called whenever the state is updated and on every render, but the component will only re-render when the returned value changes.
function MyComboboxItem({ store, id }) {
// This component will only re-render when isActive becomes true or false,
// rather than on any activeId change.
}
Reading the state on events
If you're reading the state inside an event handler, you don't need to use useStoreState
. You can read the current state directly from the store using store.getState()
, which won't trigger a re-render on the component.
function handleKeyDown(event) {
const { value } = combobox.getState();
console.log(value);
}
Writing the state
Component stores have a generic setState
method that can be used to mutate any state in the store. This method shouldn't be called during render, but it's safe to call it inside an event handler or React effect callbacks.
function onClick() {
dialog.setState("open", true);
}
The second parameter of store.setState()
can be either the new state value or a function that receives the current state and returns the new state. This is useful when you need to update the state based on a previous value.
dialog.setState("open", (open) => !open);
Component stores may also expose specific methods to update the state. These methods are named after the state property they update. For example, store.setOpen()
updates the open
state.
function onClick() {
// Equivalent to dialog.setState("open", true);
dialog.setOpen(true);
}
Like store.setState()
, these methods can also receive a function:
dialog.setOpen((open) => !open);
For consistency, all the state setters that can be passed to the store as an argument are also exposed as methods.
For convenience, component stores may also expose methods that perform specific state updates:
dialog.show(); // dialog.setOpen(true)
dialog.hide(); // dialog.setOpen(false)
dialog.toggle(); // dialog.setOpen((open) => !open)
Using React Context
When you need to access the store in child components, passing it as a prop is usually the most straightforward approach. However, if the component is deeply nested within the component tree or if you're unable to pass the store as a prop for some reason, you can leverage React Context instead:
function RequiredInput(props) {
if (!form) {
throw new Error("RequiredInput must be used within a Form component");
}
form.useValidate(() => {
if (!form.getValue(props.name)) {
form.setError(props.name, "This field is required");
}
});
}
Next steps
Continue reading our Guide to learn more about Ariakit: