vwf .

Learn - Section: 3

Progressive tutorial. Start with Getting Started and work your way through each section

Making It Interactive

1. Creating State (5 min)

VanJS uses van.state() to create reactive state. When state changes, the UI automatically updates. No hooks, no setState - just direct assignment!

import van from 'vanjs-core';

// Create state for different data types
const count = van.state(0);           // Number
const name = van.state('');           // String
const isValid = van.state(false);     // Boolean
const items = van.state([]);          // Array
const user = van.state({ name: '' }); // Object
💡Important: State holds primitives, objects, or arrays. Each piece of data gets its own van.state().
✅Best Practice: Keep state simple - one value per van.state() is best for debugging.

2. Reading State (5 min)

Access the current value with .val. Use arrow functions to make UI reactive to changes:

const count = van.state(5);

// Read current value
console.log(count.val);  // 5

// In the DOM - two ways:

// ❌ STATIC - Won't update when count changes
div(`Count: ${count.val}`)

// ✅ REACTIVE - Updates automatically
div(() => `Count: ${count.val}`)

// State as direct child - AUTOMATICALLY reactive!
div('Count: ', count)  // No arrow function needed!
⚠️Warning: Using count.val directly in DOM creates a static snapshot. Use arrow functions () => or pass state directly for reactivity!
💡Important: VanJS is smart: State passed directly as a child is automatically reactive. Arrow functions are only needed for template literals or computations.

3. Updating State (5 min)

Update state by assigning to .val. The UI updates automatically - no special function calls needed:

const count = van.state(0);
const name = van.state('');

// Direct assignment
count.val = 5;
name.val = 'Alice';

// Increment/decrement
count.val++;
count.val--;

// Use current value
count.val = count.val * 2;

// Update in event handlers
button(
  {
    onclick: () => count.val++
  },
  'Increment'
)

// Arrays - replace entire array
const items = van.state([1, 2, 3]);
items.val = [...items.val, 4];  // Add item
items.val = items.val.filter(x => x !== 2);  // Remove item
✅Best Practice: Direct assignment is the VanJS way! No setState(), no dispatch(), just: state.val = newValue
⚠️Warning: For arrays/objects, assign a new array/object to trigger reactivity: items.val = [...items.val, newItem]

4. Derived State (5 min)

Create computed values using arrow functions. They automatically re-run when dependencies change:

const count = van.state(5);

// Derived values - arrow functions
const doubled = () => count.val * 2;
const message = () => count.val > 10 ? 'High' : 'Low';

// Use in DOM
div(
  p('Count: ', count),
  p(() => `Doubled: ${doubled()}`),
  p(() => `Status: ${message()}`)
)

// Multiple dependencies
const firstName = van.state('John');
const lastName = van.state('Doe');
const fullName = () => `${firstName.val} ${lastName.val}`;

div(() => `Hello, ${fullName()}`);
💡Important: Derived state is just a function! Call it with () to get the current value. VanJS tracks dependencies automatically.
✅Best Practice: Use derived state for computed values instead of creating more van.state(). Simpler and more efficient!

5. Conditional Rendering (5 min) ⚠️ CRITICAL

This is the #1 cause of bugs in VanJS! Read carefully:

const showModal = van.state(false);
const hasError = van.state(false);

// ❌ WRONG - Renders "false" as text!
() => showModal.val && Modal()
// When showModal is false, this returns false
// VanJS converts false to string "false" and shows it!

// ✅ CORRECT - Returns empty string
() => showModal.val ? Modal() : ''

// ✅ Multiple conditions
() => {
  if (loading.val) return Spinner();
  if (error.val) return ErrorMessage();
  return Content();
}

// ✅ Complex conditionals
() => (
  isLoggedIn.val
    ? hasPermission.val
      ? AdminPanel()
      : UserPanel()
    : LoginForm()
)
⚠️Warning: NEVER use && for conditionals in VanJS! It returns false which renders as "false" text. ALWAYS use ternary: condition ? Component() : ''

🚨 Common Mistake

// This shows "false" on screen when hidden!
() => isVisible.val && div('Content')

// Always use ternary instead:
() => isVisible.val ? div('Content') : ''
✅Best Practice: Return empty string '' for "nothing". Never return null, undefined, or false!

🎯 State & Reactivity Checklist

  • ✅van.state() creates reactive state
  • ✅Read with .val, update with assignment
  • ✅Arrow functions for derived values
  • ✅State as direct child = automatic reactivity
  • ✅Arrow functions for template literals
  • ✅NEVER use && for conditionals
  • ✅ALWAYS use ternary: condition ? Component() : ''

Explore vwf

Discover what vwf can do