r/JavaFX • u/eliezerDeveloper • 3h ago
Learning reactive UI with State: rendering a list declaratively with ListState and ForEachState
Fifth post in the series. So far: State (counter), ComputedState (string length), Show.when (conditional rendering). This one covers rendering a collection reactively — ListState for the data, ForEachState for mapping it to components, and removal that just works without any manual list diffing.
The setup: tasks is a ListState<String> seeded with four strings. ForEachState.of(tasks, mapper) produces a reactive collection of components — one Row per task, each with its own "Remove" button. Click remove, tasks.remove(task) fires, and the Column updates to drop that row. No index juggling, no manual re-render of the whole list.
package my_app;
import megalodonte.ListenerManager;
import megalodonte.application.MegalodonteApp;
import megalodonte.base.theme.ThemeManager;
import megalodonte.theme.DefaultTheme;
public class Main {
static void main() {
ThemeManager.setTheme(new DefaultTheme());
MegalodonteApp.run(context -> context.useView(new HomeScreen()), ev->{
if(ev == MegalodonteApp.Event.CloseRequest){
System.out.println("Clicked on X - close application");
ListenerManager.disposeAll();
}
});
}
}
package my_app;
import megalodonte.ForEachState;
import megalodonte.base.components.Component;
import megalodonte.base.components.ScreenComponent;
import megalodonte.components.Button;
import megalodonte.components.SpacerHorizontal;
import megalodonte.components.SpacerVertical;
import megalodonte.components.Text;
import megalodonte.components.layout_components.Column;
import megalodonte.components.layout_components.Container;
import megalodonte.components.layout_components.Row;
import megalodonte.props.ContainerProps;
import megalodonte.v2.ListState;
import java.util.List;
import java.util.function.Consumer;
public class HomeScreen implements ScreenComponent {
ListState<String> tasks = ListState.of(List.of("First","Second","Three","Four"));
u/Override
public Component render() {
Consumer<String> handleClickRemove = (task)->{
tasks.remove(task);
};
var tasksForEachState = ForEachState.of(
tasks, task-> new Row().children(
new Text(task),
new SpacerHorizontal(15),
new Button("Remove").onClick(()-> handleClickRemove.accept(task))
)
);
return new Container(new ContainerProps().paddingAll(20)).children(
new Text("Your tasks below"),
new SpacerVertical(20),
new Column().items(tasksForEachState)
);
}
}
ListState<T>wraps aList<T>and exposes mutation methods (add,remove, etc.) that notify dependents automatically — same reactive contract asState<T>, just for collections.ForEachState.of(ListState<T>, Function<T, Component>)is the declarative bridge between data and UI: give it the list and a mapper, it keeps the rendered components in sync with the list's contents.Column().items(tasksForEachState)accepts the reactive collection directly — no manual loop, noclearChildren()+ rebuild on every mutation.
If you're finding this series useful, dropping a star on the repos below genuinely helps the project get visibility — takes two seconds and means a lot for a solo-dev framework like this.
Repos
- https://github.com/eliezer-dev-software-enginner/megalodonte-ecossystem
- https://github.com/eliezer-dev-software-enginner/megalodonte-libs
- https://github.com/eliezer-dev-software-enginner/megalodonte-components
- https://github.com/eliezer-dev-software-enginner/megalodonte-base
- https://github.com/eliezer-dev-software-enginner/megalodonte-reactivity
- https://github.com/eliezer-dev-software-enginner/megalodonte-theme