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 |
.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>
.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.
getState()
pphp.getState(key)
retrieves a scoped reactive value by key.
It first searches the current component scope, then walks up the parent scopes, and finally falls back to
shared state (pphp.share
).
The return value is a callable + proxy:
you can call()
it to read, use dot-notation to access properties, and update via .set()
.
Signature
getState<T = any>(key: string): T
& ((...args: any[]) => T)
& { set: (value: T | ((prev: T) => T)) => void }
Lookup order
- Current scope:
ComponentA.ComponentB.key
- Parents (upwards):
ComponentA.key
, thenkey
- Shared: if registered via
pphp.share('key', ...)
Basic usage
<script>
// Grab reactive state (scoped/parent/shared)
const isValid = pphp.getState('isValid');
// β
Read (callable)
console.log('valid?', isValid());
// β
Update (direct or functional)
isValid.set(true);
isValid.set(prev => !prev);
// β
In objects: dot notation is reactive
const form = pphp.getState('form');
console.log(form.user.name); // property access
form.user.name = 'Abraham'; // property write triggers update
// β οΈ PRIMITIVES in <script>: use .value for strict checks
if (isValid.value === true) {
console.log('Strict check passed');
}
</script>
With templates
In Mustache bindings, you can use the state naturally (no .value
needed).
<div class="text-sm">
Status: {{ isValid ? 'Valid' : 'Invalid' }}
<br />
User: {{ form.user.name }}
</div>
<script>
const isValid = pphp.getState('isValid');
const form = pphp.getState('form');
</script>
Scope awareness example
When called from a child component, getState('key')
first checks the childβs scope
(e.g., Parent.Child.key
), then the parent (Parent.key
), then global (key
), then shared.
<!-- Parent sets form -->
<script>
const form = pphp.getState('form');
form.user = { id: 1, name: 'Ada' };
</script>
<!-- Child reads same key; resolves up to parent if not present locally -->
<script>
const form = pphp.getState('form');
console.log(form.user.name); // 'Ada'
</script>
Initialize after hydration
Combine with pphp.onHydrationComplete()
to run code only when reactive values are guaranteed ready.
<script>
const isValid = pphp.getState('isValid');
const form = pphp.getState('form');
pphp.onHydrationComplete(() => {
// Safe to read/measure or seed defaults
if (!form.user?.name) {
form.user = { id: 1, name: 'Abraham' };
}
if (isValid() == null) {
isValid.set(false);
}
});
</script>
Behavior notes
- Callable read:
state()
β current value; reactivity is tracked. - Dot-notation: If the value is an object, its properties are proxied and reactive.
- Updates:
state.set(value)
or functionalstate.set(prev => ...)
. - Late discovery: If the key isnβt found yet, a short retry window watches for it and triggers a flush when it appears.
- Primitives in