Controlled State

How to work with controlled vs. uncontrolled state in Akar.

Akar provides flexible state management for components, allowing developers to use either controlled or uncontrolled state. Understanding when to use each approach ensures better integration with Vue's reactivity system.


Controlled vs. Uncontrolled State

Controlled State

A controlled component receives its state as a prop and requires explicit updates via event listeners. The parent component manages and synchronizes the state.

Example: Controlled ASwitchRoot

<script setup>
import { ASwitchRoot, ASwitchThumb } from 'akar';
import { ref } from 'vue';

const isActive = ref(false);

function handleUpdate(value) {
  isActive.value = value;
}
</script>

<template>
  <ASwitchRoot
    :model-value="isActive"
    @update:model-value="handleUpdate"
  >
    <ASwitchThumb />
  </ASwitchRoot>
</template>

How it works:

  • The ASwitchRoot component’s state is managed by the isActive ref.
  • The @update:modelValue event ensures updates propagate correctly.
Use controlled state when:
  • You need to sync state with Vuex, Pinia, or an API.
  • Multiple components rely on the same state.
  • You want fine-grained control over updates.

Using v-model with Controlled Components

Vue’s v-model syntax provides a convenient way to bind values to controlled components in Akar. It automatically handles passing the value and listening for updates.

Example: Using v-model with ASwitchRoot

<script setup>
import { ASwitchRoot, ASwitchThumb } from 'akar';
import { ref } from 'vue';

const isActive = ref(false);
</script>

<template>
  <ASwitchRoot v-model="isActive">
    <ASwitchThumb />
  </ASwitchRoot>
</template>

Uncontrolled State

An uncontrolled component manages its own state internally, without requiring a parent-controlled prop. Instead of modelValue, Akar components use defaultValue to initialize state.

Example: Uncontrolled ASwitchRoot

<template>
  <ASwitchRoot default-value="true">
    <ASwitchThumb />
  </ASwitchRoot>
</template>

How it works:

  • The ASwitchRoot initializes its state with defaultValue.
  • State changes occur internally without external control.
Use uncontrolled state when:
  • The component does not need to sync with external logic.
  • You want a simpler setup without explicit state management.
  • The state is local and does not impact other components.

Common Mistakes & Fixes

1. Forgetting @update:modelValue

<!-- ❌ Incorrect: -->
<ASwitchRoot :modelValue="isActive" />

<!-- ✅ Correct: -->
<ASwitchRoot :modelValue="isActive" @update:modelValue="(val) => isActive = val" />

2. Using modelValue Instead of defaultValue

<!-- ❌ Incorrect: -->
<ASwitchRoot :modelValue="true" />

<!-- ✅ Correct: -->
<ASwitchRoot defaultValue="true" />

3. Not Providing a Setter for Computed Props

// ❌ Incorrect:
const isActive = computed(() => store.state.toggleState);

// ✅ Correct:
const isActive = computed({
  get: () => store.state.toggleState,
  set: (val) => store.commit('setToggleState', val)
});