State Management
JWeb provides reactive state management inspired by React hooks. State changes automatically trigger UI updates via WebSocket.
Overview
Use useState() to create reactive state variables. When state changes, the component re-renders automatically.
import static com.osmig.Jweb.framework.state.StateHooks.*;
// Create state with initial value
State<Integer> count = useState(0);
// Read: count.get()
// Write: count.set(newValue)
// Update: count.update(current -> newValue)Creating State
Use useState to create reactive state in components.
import static com.osmig.Jweb.framework.state.StateHooks.*;
public class Counter implements Template {
private final State<Integer> count = useState(0);
public Element render() {
return div(
h1("Count: " + count.get()),
button(attrs().onClick(e -> count.set(count.get() + 1)),
text("Increment")),
button(attrs().onClick(e -> count.set(count.get() - 1)),
text("Decrement"))
);
}
}Reading State
Use get() to read the current value.
State<String> name = useState("John");
State<Integer> age = useState(25);
State<Boolean> active = useState(true);
// Read values
String currentName = name.get();
int currentAge = age.get();
boolean isActive = active.get();
// Use in templates
p("Name: " + name.get())
p("Age: " + age.get())
when(active.get(), () -> span("Active"))Setting State
Use set() to replace the value, update() to transform it.
State<String> name = useState("John");
State<Integer> count = useState(0);
// Direct set
name.set("Jane");
count.set(10);
// Update based on current value
count.update(c -> c + 1);
count.update(c -> c * 2);
// Toggle boolean
State<Boolean> visible = useState(false);
visible.update(v -> !v);Batch Updates
Multiple updates in one event handler are batched.
State<Integer> x = useState(0);
State<Integer> y = useState(0);
button(attrs().onClick(e -> {
x.set(100); // Batched
y.set(200); // Batched
// Single re-render after both updates
}), text("Move"))Conditional Updates
State<Integer> count = useState(0);
// Only update if condition met
button(attrs().onClick(e -> {
if (count.get() < 10) {
count.update(c -> c + 1);
}
}), text("Increment (max 10)"))
// Reset to initial
button(attrs().onClick(e -> count.set(0)), text("Reset"))State with Objects
Store complex objects in state.
// User record or class
record User(String name, String email, boolean admin) {
User withName(String name) {
return new User(name, email, admin);
}
User withEmail(String email) {
return new User(name, email, admin);
}
}
State<User> user = useState(new User("John", "john@test.com", false));
// Update object
user.update(u -> u.withName("Jane"));
user.update(u -> u.withEmail("jane@test.com"));
// Display
div(
p("Name: " + user.get().name()),
p("Email: " + user.get().email()),
when(user.get().admin(), () -> span("Admin"))
)Nested Object Updates
record Address(String street, String city) {}
record Person(String name, Address address) {}
State<Person> person = useState(
new Person("John", new Address("123 Main", "NYC"))
);
// Update nested property - create new objects
person.update(p -> new Person(
p.name(),
new Address("456 Oak", p.address().city())
));State with Lists
Manage collections with reactive state.
State<List<String>> items = useState(new ArrayList<>());
// Add item
items.update(list -> {
list.add("New item");
return list;
});
// Remove item
items.update(list -> {
list.remove(index);
return list;
});
// Filter items
items.update(list -> list.stream()
.filter(item -> !item.isEmpty())
.collect(Collectors.toList()));Todo List Example
record Todo(String text, boolean done) {}
State<List<Todo>> todos = useState(new ArrayList<>());
// Add todo
void addTodo(String text) {
todos.update(list -> {
list.add(new Todo(text, false));
return list;
});
}
// Toggle todo
void toggleTodo(int index) {
todos.update(list -> {
Todo old = list.get(index);
list.set(index, new Todo(old.text(), !old.done()));
return list;
});
}
// Render
ul(each(todos.get(), (todo, i) ->
li(
input(attrs().type("checkbox")
.checked(todo.done())
.onChange(e -> toggleTodo(i))),
span(todo.text())
)
))Derived State
Compute values from other state.
State<List<Todo>> todos = useState(new ArrayList<>());
// Derived values (recomputed on render)
int total = todos.get().size();
int completed = (int) todos.get().stream()
.filter(Todo::done).count();
int remaining = total - completed;
div(
p("Total: " + total),
p("Completed: " + completed),
p("Remaining: " + remaining)
)Shared State
Share state between components via constructor.
// Parent owns the state
public class App implements Template {
private final State<User> user = useState(null);
public Element render() {
return div(
new Header(user), // Pass state
new Content(user), // Same state
new Footer(user) // Same state
);
}
}
// Child receives and uses state
public class Header implements Template {
private final State<User> user;
public Header(State<User> user) {
this.user = user;
}
public Element render() {
return header(
when(user.get() != null,
() -> span("Welcome, " + user.get().name()))
);
}
}State changes trigger automatic re-renders via WebSocket.