Quick Reference
State
Reactive state used to store values that can change over time and automatically update the UI.
Syntax
const value = state(initialValue);Returns
state() returns a function used to read the current value.
It also includes a:
.set(newValue | (prev) => newValue)method used to update the state.
Example
function App() {
const count = state(0);
const handleClick = () => {
count.set(18); // direct value
count.set((p) => p + 1); // update from previous value
};
return <button onClick={handleClick}>You Clicked {count()} times!</button>;
}Reactive Access
Calling count() inside JSX always returns the latest value.
When the state changes, the UI updates automatically.
List state
Reactive array state with built-in mutation helpers for predictable UI updates.
Syntax
const items = state(list(initialValue));Features
list() provides helper methods to modify arrays while keeping the UI in sync.
Instead of replacing the whole array manually, you can directly:
- add items
- update items
- remove items
- reset the list
Unlike regular arrays, reactive list rendering should use <For /> instead of .map() for better control, predictable updates, and performance.
Example
function App() {
const users = state(list([]));
return (
<div>
<button onClick={() => users.push({ name: "John" })}>Add User</button>
<button
onClick={() =>
users.update(0, {
name: "Updated User",
})
}
>
Update First
</button>
<button onClick={() => users.remove(1)}>Remove Second</button>
<button onClick={() => users.set([])}>Reset</button>
{/* ...other helper methods */}
<ul>
<For each={users()}>{(user, i) => <li>{user.name}</li>}</For>
</ul>
</div>
);
}Why use list()?
Regular arrays are not reactive.
Calling methods like .push() or .splice() directly will not reliably update the UI.
list() provides controlled mutations such as:
push()update()remove()set()- and more...
These methods keep rendering predictable and synchronized.
Internally, list methods apply direct updates instead of diffing the entire array. This avoids unnecessary re-renders and does not rely on keys for normal operations.
For predictable updates (add, update, remove), prefer list methods.
For complex transformations like filtering or remapping, use:
users.set(newArray);set() operations with reordered items may require keys.
Global store/states
Create reactive global state shared across the entire application.
createStore()
const store = createStore({
key: value,
});Features
Global stores can contain:
- normal reactive state
- list state using
list() - shared application data
Store values can be accessed from any component using useGlobal().
Creating a Store
import { createStore, list } from "@vebeljs/vebel";
export const store = createStore({
user: {
name: "Kevin",
username: "kevi73",
},
posts: list([]),
});Using Global State
function Profile() {
const { posts, user } = useGlobal(store);
return (
<div>
<h2>{user().name}</h2>
<p>Username: {user().username}</p>
<p>Total posts: {posts.size}</p>
</div>
);
}Notes
- Global state is reactive.
- Components update automatically when used store values change.
list()works the same way as local list state.
Unique store keys
Store keys must be globally unique across all stores.
This will throw an error:
const store1 = createStore({
user: {},
});
const store2 = createStore({
user: {},
});Because the user key already exists in another store.
Preserved state
Create reactive state that persists across page reloads.
preservedState()
const value = preservedState(key, initialValue);Features
preservedState() behaves like normal state().
value();
value.set(newValue);The difference is that its value is preserved even after a hard page reload.
This is useful for restoring temporary UI state such as:
- scroll position
- active tabs
- form progress
- sidebar state
- filters
Example
const scrollPos = preservedState("scroll-pos", 0);
window.addEventListener("scroll", () => {
scrollPos.set(window.scrollY);
});
window.scrollTo(0, scrollPos());In this example:
- user scrolls to
700 - page reloads
- value remains preserved
- scroll can be restored manually
preservedState() only stores the value.
The restore logic must be handled by the user.
Avoid overusing preserved state
Persisted values may affect performance if overused.
Avoid storing:
- very large arrays
- deeply nested objects
- heavy application data
Although supported, preserve large data only when truly necessary.
Reactive props
Access reactive parent state inside child components.
fromParent()
fromParent(componentName?)Access Types
fromParent() provides access to reactive values exposed by parent components.
Available groups:
fromParent().state;
fromParent().list;You can access values from:
- immediate parent
- specific ancestor component
Example
function Child() {
// normal state
const { count } = fromParent().state;
// list state
const { users } = fromParent().list;
// access from specific ancestor
const { product } = fromParent("Product").state;
const increaseQuantity = () => {
product.set((p) => ({
...p,
quantity: p.quantity + 1,
}));
};
return (
<div>
<p>Total users: {users.size}</p>
<p>Count: {count()}</p>
<button onclick={increaseQuantity}>Increase product quantity</button>
</div>
);
}
function App() {
const num = state(0, "count");
return (
<>
<p>{num()}</p>
<Child />
</>
);
}Named state access
Reactive values must be created with a name to be accessible through fromParent().
const count = state(0, "count");Without a name, the state cannot be accessed by child components.
Component updates
Child components are not affected by normal parent re-renders.
They only update when the reactive values they use actually change.
Effects
Runs side effects after the component renders.
Syntax
setEffect(callback, dependencies?, options?);Behavior
setEffect() is commonly used for:
- logging
- API calls
- subscriptions
- timers
- DOM interactions
By default, the effect runs once after the first render.
If dependencies are provided, the effect re-runs whenever those values change.
Basic Example
function App() {
setEffect(() => {
console.log("Component mounted");
});
return <p>Hello</p>;
}Cleanup Function
You can return a cleanup function from the effect.
The cleanup runs before unmounting or before the effect re-runs.
function App() {
setEffect(() => {
console.log("Component mounted");
return () => {
console.log("Cleanup | Component Unmounted");
};
});
return <p>Hello</p>;
}Dependency Example
Effects re-run whenever one of their dependencies changes.
function App() {
const count = state(0);
setEffect(() => {
console.log("Count changed:", count());
}, [count]);
return (
<button onClick={() => count.set((p) => p + 1)}>Count: {count()}</button>
);
}Async Example
function App() {
setEffect(async () => {
const data = await fetch("/api");
console.log(data);
});
return <p>Loading..</p>;
}Async inside setEffect()
If you use async directly inside setEffect(), do not return a cleanup function.
Async functions always return a Promise, which conflicts with cleanup behavior.
If you need both async logic and cleanup, wrap async code inside a normal function.
function App() {
setEffect(() => {
async function load() {
await fetch("/api");
}
load();
return () => {
console.log("Cleanup");
};
});
return <p>Hello</p>;
}DOM Refs
Create a reference to access DOM elements directly.
useElementRef()
const ref = useElementRef(tag?);Features
useElementRef() is mainly used for:
- direct DOM access
- element measurements
- focus management
- scrolling
- manual DOM manipulation
Unlike state, refs do not trigger re-renders.
They are meant for storing element references, not reactive UI values.
Values stored inside refs persist across renders without causing updates.
Basic Example
function App() {
const divRef = useElementRef("div");
setEffect(() => {
divRef.el.style.backgroundColor = "navy";
});
return <div ref={divRef}>...</div>;
}Returned Object
{
(el, _tag);
}el: The attached DOM element.
_tag: The expected element tag type.
Example:
const divRef = useElementRef("div");returns:
{
el: HTMLDivElement,
_tag: "div"
}Providing a tag enables proper element type suggestions such as:
divRef.el.style;
divRef.el.scrollTop;
divRef.el.focus();Generic Usage
If no tag is provided, the ref can attach to any element.
const ref = useElementRef();Restrictions
Ref limitations
A ref instance can only be attached to a single element.
<div ref={myRef} />
<button ref={myRef} /> // invalidTyped refs can only be attached to matching elements.
const divRef = useElementRef("div");
<div ref={divRef} /> // valid
<button ref={divRef} /> // invalidLazy-load and Await
Lazy-load components using dynamic imports.
load()
const Component = load(
importFunction,
exportKey?
);Features
load() creates an async component that loads only when needed.
This helps reduce initial bundle size and improves loading performance.
The returned component can only be rendered inside <Await />.
Parameters
importFunction
Function returning a dynamic import.
() => import("./Component");exportKey
Name of the exported component to load.
load(() => import("./Comp"), "Navbar");If omitted, "default" is used automatically.
Default Export Example
const Navbar = load(
() => import("../components/Navbar"),
// take "default" as default argument automatically
);Used for:
export default Navbar;Named Export Example
const Navbar = load(() => import("../components/Navbar"), "Navbar");Used for:
export { Navbar };Await
Render fallback UI while async components are loading.
<Await loader={LoaderComponent}>
<AsyncComponent />
</Await>Features
<Await /> handles rendering async components created using load().
The loader prop renders temporary UI until all async imports resolve.
Basic Example
const Navbar = load(() => import("../components/Navbar"));
function App() {
return (
<Await loader={() => <h1>loading...</h1>}>
<Navbar />
</Await>
);
}Multiple Async Components
<Await /> can handle multiple async components together.
<Await loader={() => "loading..."}>
<Navbar />
<Footer />
</Await>The loader remains visible until all async components finish loading.
Async component restriction
Components created using load() must be rendered inside <Await />.
<Navbar /> // invalid
// valid
<Await loader={() => "loading..."}>
<Navbar />
</Await>Portal
Render UI outside the main DOM tree while keeping it connected to the component hierarchy.
Syntax
<Portal target={element?}>
content
</Portal>Features
Content inside <Portal /> renders outside the normal parent DOM structure but still behaves as part of the same application tree.
This is useful for:
- modals
- dialogs
- tooltips
- dropdowns
- overlays
Portals help avoid:
- z-index conflicts
- overflow clipping
- positioning issues
Basic Example
function App() {
return (
<Portal>
<div className="modal">Modal Content</div>
</Portal>
);
}By default, portal content is rendered into:
document.body;Custom Target
Use the target prop to render into a specific element.
Behavior
Even though the DOM is rendered outside the main tree:
- state still works
- events still work
- context remains connected
- reactivity is preserved
The portal behaves like a normal part of the component tree.
Common usage
<Portal /> is primarily designed for overlays and floating UI where normal DOM nesting causes layout or stacking issues.
Route Effect
Run logic whenever the current route changes.
useRouteEffect()
useRouteEffect((path) => {
// route changed
});Features
useRouteEffect() is mainly useful inside layout components or persistent wrappers that do not re-render during navigation.
It allows you to react to route changes even when the component itself stays mounted.
Example
function DocsLayout({ Child }) {
useRouteEffect((path) => {
console.log(
"Route changed:",
path(), // returns the current active route.
);
});
return (
<div>
<Navbar />
<Child />
</div>
);
}Why use useRouteEffect()?
Best place to use
Layout components usually remain mounted between route transitions.
Because of that, normal lifecycle logic may not run again during navigation.
useRouteEffect() solves this by running whenever the route changes.
useRouteEffect() is most useful inside shared layouts and persistent components that survive route transitions.