Hi!
In React, components are classes, so they have state. State is just an object that can hold any values of any type.
What's the right pattern for stateful components in Karax? One option is to hold component states in global values that are mutated by the procs that render the components. But polluting the global namespace looks like a bad pattern. I tried passing mutable values into the render procs, but as soon as I add var to the param type and try to mutate it inside buildHtml macro, I get illegal capture error.
Here's one approach to save a button's state (it's a reimplementation of the official React getting started tutorial):
import future, tables
include karax / prelude
var likeButton: tuple[liked: bool]
proc buildLikeButton(): VNode =
buildHtml:
if likeButton.liked:
text "You liked this"
else:
button(onclick = () => (likeButton.liked = true)):
text "Like"
proc createDom(): VNode =
buildHtml(tdiv):
buildLikeButton()
setRenderer createDom
The best way I've found is to have a single global state variable in your app's root component (typically the component that handles routing). Then define a State type in each module that you initialise and pass to each underlying component in the root component.
The forum sort of works this way. There are some components that still use globals when they shouldn't, but thats just a result of me experimenting. I'll need to fix this eventually. It's still a good idea to take a look at the nimforum source code though as an example.
It's still an open question what the "best" way to do components for Karax is. That said, this whole DOM diffing concept is only slightly more advanced than the "immediate mode" UIs that are popular for computer games. And an immediate mode UI is in contradiction with stateful components.
You have an external state and derive your UI from this state. This external state is naturally "global" or at least not part of the (virtual) DOM.
I agree that having stateful components are necessarily the right approach. Also, the way OOP in Nim is designed, attributes and methods are syntactically separated anyway, so the state and the procs that mutate it have to be independent. I actually think it's quite nice.
However, I think to have a clean separation between state and mutators, it would be nice to have a way to pass the state as mutable argument to mutating procs. And for some reason it can't be done now:
proc buildLikeButton(liked: var bool): VNode =
buildHtml:
if likeButton.liked:
text "You liked this"
else:
button(onclick = () => (liked = true)):
text "Like"
gives illegal capture.
Here is how you should do it:
type
LikeButton = ref object
liked: bool
proc buildLikeButton(state: LikeButton): VNode =
buildHtml:
if state.liked:
text "You liked this"
else:
button(onclick = () => (state.liked = true)):
text "Like"
Thanks for the tip. Unfortunately, I couldn't make it work this way:
import future, tables
include karax / prelude
type
LikeButton = ref object
liked: bool
proc buildLikeButton(state: LikeButton): VNode =
buildHtml:
if state.liked:
text "You liked this"
else:
button(onclick = () => (state.liked = true)):
text "Like"
proc createDom(): VNode =
var likeButton = new LikeButton
buildHtml(tdiv):
h1:
text "Hello Karax"
buildLikeButton(likeButton)
setRenderer createDom
On every render your createDom is called which produces a new LikeButton. (Read your own code, it literally says that. ;-) )
:-D Gee, I even remember thinking this exact thought when I first planned to post this code sample. My bad :-)
This gist shows from a previous forum question demonstrates the Elm architecture. Components are stateless and pure functions are used to update state. This approach can scale quite well.
https://gist.github.com/honewatson/d60cf89804607dbeb652841b484441dc