state

The state() function creates a reactive variable with enhanced primitive-like behavior and returns a getter/setter pair. It enables you to store, read, and update local data with automatic DOM reactivity when values change.

⚠️ Convention: In <script> blocks, always use .value when working with primitive states. This is because JavaScript Proxies cannot fully emulate primitive assignment.
✅ In Mustache templates {{ ... }}, .value is not required.

Use Cases

Use state() when you want to bind dynamic data in your templates or react to changes with effect(). It's perfect for form fields, counters, toggles, and local variables scoped to a section or component.

Syntax

const [count, setCount] = pphp.state(0);
// or initialize without destructuring (read-only)
// Note: When using the read-only form, you cannot update the state or use it with effect methods that require a setter.
pphp.state('theme', 'light');
  • key: A string that uniquely identifies the state variable. Must not collide with reserved native objects.
  • initial: The initial value of the state (can be any type: string, number, boolean, object, etc).
  • Returns: A tuple [getter, setter] where:
    • getter() returns the current value (for reactivity) and can be used directly in bindings.
    • setter(value) updates the value, or accepts a function for functional updates.

Access Rules

Context Primitive (string, number, boolean) Object / Array Requires .value?
{{ mustache bindings }} ✅ Works ✅ Works No
console.log() ✅ Prints value directly ✅ Prints value directly No
<script> (arithmetic, if, comparisons) ❌ Requires .value ✅ Direct access Yes (primitives only)
Strict checks (===, typeof, instanceof) ❌ Requires .value ❌ Requires .value Yes
📌 Best Practice: • In templates, forget about .value, everything works naturally.
• In <script> blocks, always use .value for primitives (string, number, boolean). • Objects/arrays work without .value for property access, but still require it for JSON / type methods.

Examples

<div>
  <p>Hello, {{ name }}</p>
  <p>Count: {{ count }}</p>
</div>

<script>
  const [name, setName] = pphp.state('John');
  const [count, setCount] = pphp.state(0);

  export const test = () => {
    // ✅ Template literals & logging
    console.log(`Hello ${name}`);  // Works without .value
    console.log(count);            // Logs primitive directly

    // ⚠️ In script: use .value
    if (count.value > 5) {
      console.log('Count is greater than 5');
    }

    // ✅ Update
    setCount(prev => prev + 1);

    // ⚠️ Strict equality
    if (name.value === 'John') {
      console.log('Exact match');
    }
  };
</script>

Objects / Arrays

<div>
  <p>User: {{ user.name }}</p>
  <p>First item: {{ items[0] }}</p>
</div>

<script>
  const [user, setUser] = pphp.state({ id: 1, name: 'Abraham' });
  const [items, setItems] = pphp.state(['apple', 'banana']);

  console.log(user.name);   // Works without .value
  console.log(items[0]);    // Works without .value

  // ❌ Requires .value for inspection
  console.log(Object.keys(user.value));
  console.log(Array.isArray(items.value));
</script>
🚀 Summary: • Mustache templates = no .value. • Console logging = no .value. • Script operations = primitives must use .value. • Objects/arrays mostly work without .value, except for type/inspection methods.

⚠️ Requires .value

  • • Strict equality: name.value === 'John'
  • • Type checking: typeof count.value
  • • Instance checking: user.value instanceof Object
  • • JSON methods: JSON.stringify(user.value)
  • • Array methods: Array.isArray(items.value)
  • • Object methods: Object.keys(user.value)

🎯 Best Practice

Use the enhanced primitive-like behavior for natural JavaScript operations (85% of use cases). Only use .value when you need strict equality, type checking, or specific built-in functions. In Mustache templates, everything works automatically.

Behavior

  • State is automatically scoped to the current component or section.
  • If the state key already exists, it reuses the existing state instead of overwriting it.
  • State keys are reactive and automatically update bound DOM elements or run dependent effect() handlers.
  • Supports functional updates: setCount(prev => prev + 1).
  • Throws an error if you use a reserved word like Object or Array.
  • Enhanced: Automatic primitive conversion using Symbol.toPrimitive for natural JavaScript operations.

Traditional Example Usage

<button onclick="increment">
  Click Me!
</button>

<p>Count is: {{ count }}</p>
<p>Label: {{ label }}</p>

<script>
  const [count, setCount] = pphp.state(0);
  const [label, setLabel] = pphp.state('Click the button');

  export const increment = () => {
    // ✅ Enhanced: Many operations work without .value
    console.log(`Current count: ${count}`);     // Template literal
    console.log(count + 1);                     // Arithmetic
    
    if (count > 5) {                             // Comparison
      setLabel('Lots of clicks!');
    } else {
      setLabel(`Clicked ${count + 1} times!`);  // Mixed operations
    }
    
    setCount(prev => prev + 1);
    
    // ❌ Strict equality still needs .value
    if (count.value === 10) {
      setLabel('Exactly 10 clicks!');
    }
  };
</script>

Read-Only Short Form

You can also use the getter without destructuring if you don't need a setter:

<body class="{{ theme }}"></body>

<script>
  const theme = pphp.state('dark');
  
  // ✅ Enhanced: Works in many contexts without .value
  console.log(`Theme is: ${theme}`);           // Template literal
  console.log(theme == 'dark');                // Loose equality
  
  if (theme == 'dark') {                       // Boolean context with conversion
    console.log('Dark mode enabled');
  }
  
  // ❌ Still need .value for strict operations
  console.log(theme.value === 'dark');         // Strict equality
  console.log(typeof theme.value);             // Type checking
</script>

🚀 New Feature Summary

With enhanced type support, Prisma PHP state variables now behave like native primitives in 85% of JavaScript operations. Use .value only when you need strict equality (===), type checking (typeof), or specific built-in functions. In Mustache bindings, everything works automatically as before.

state object with functions

You can initialize state() with an object that also contains its own methods like set and reset. This allows encapsulating related logic directly in the state. With enhanced type support, you can call these methods naturally.

<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>

<button class="btn btn-primary btn-sm mt-2" onclick="setUserData">
    Set Data
</button>
<button class="btn btn-secondary btn-sm mt-2 ml-2" onclick="resetUser">
    Reset
</button>

<script>
  const [user, setUser] = pphp.state({
    name: "",
    age: "",
    set(data) {
      this.name = data.name;
      this.age = data.age;
    },
    reset() {
      this.name = "";
      this.age = "";
    }
  });

  export const setUserData = () => {
    // ✅ Enhanced: Direct method calls work naturally
    user.set({ name: "Abraham", age: "30" });
    
    // ✅ Enhanced: Property access without .value
    console.log(`User set to: ${user.name}, age ${user.age}`);
    
    // ✅ Enhanced: Conditional logic without .value
    if (user.name && user.age) {
      console.log('User data is complete');
    }
  };

  export const resetUser = () => {
    user.reset();
    
    // ✅ Enhanced: Check if reset worked
    if (!user.name && !user.age) {
      console.log('User data cleared');
    }
  };
</script>

Notice that you can call user.set(...) and user.reset() directly on the object returned by state(). With enhanced type support, you can also access properties and use the object in JavaScript operations naturally without .value in most contexts.