I'm currently testing a Karax app structure where each component is a module that has internal state stored in the module's global variables and that exports render proc, which is called in the root component.
For example, my likebutton.nim component look like this:
import future
include karax/prelude
var liked = false
proc render*(): VNode =
buildHtml:
if liked:
text "You liked this"
else:
button(onclick = () => (liked = true)):
text "Like"
and the root component (index.nim) looks like this:
include karax/prelude
from likebutton import nil
proc render(): VNode =
buildHtml(tdiv):
h1:
text "Hello Karax!"
likebutton.render()
when isMainModule:
setRenderer render
The idea is to import all components and call their render procs when needed; root's own render proc renders the entire app and is set globally as the renderer.
Unfortunately, the code above fails to compile: index.nim(10, 15) Error: Expected a node of kind nnkIdent, got nnkDotExpr
Is this an issue to be fixed or a mistake on my side? What would be the right way to split an app?
The bug you're running into is a known one. Although it seems I forgot to submit it as an issue on GitHub :)
The workaround is simple: render(likebutton). So I've just learned to do that instead of spending time fixing this (it's probably a trivial fix, but you know how it is when you need to context switch).
Your component is still incorrectly structured IMO. Do not put global state in your components (only in your root), that prevents reuse. For each component get in the habit of declaring a State type (calling it LikeButton or ComponentName etc.) together with an appropriately named newState constructor.
In your index.nim you can then have a global State type:
type
State = ref object
likeBtn: LikeButton
proc newState() =
State(likeBtn: newLikeButton())
var state = newState()
proc render(): VNode =
buildHtml(tdiv):
h1:
text "Hello Karax!"
render(state.likeBtn)
Do not put global state in your components (only in your root), that prevents reuse.
The approach I'm testing doesn't imply that. Likebutton is a blackbox, it's state is invisible and unimportant to the root component. Yeah, it's in a global variable, but it's global only in the component module, it's not exported.
Putting the state in a dedicated type instead of a bunch of variables is probably a good idea, but there's this annoying boilerplate with newState in each component.
The idea I am testing is that component state is its own business, and the root component should probably never care about it. If a component absolutely needs to share its state, it exports the necessary fields of its State type. This is slightly different from your approach where components submit their state to the root by default (or at least that's how I understand it so far).
The bug you're running into is a known one. Although it seems I forgot to submit it as an issue on GitHub :)
Iti is now my duty not to forget to submit it too :-) The fix should not be too complicated it seems.
As a side note, I wonder if we should set up a dedicated Karax forum? :)
Maybe introduce subforums or tags instead? Our community isn't large enough to split it between multiple forums :-)
The approach I'm testing doesn't imply that. Likebutton is a blackbox, it's state is invisible and unimportant to the root component. Yeah, it's in a global variable, but it's global only in the component module, it's not exported.
By all means play around with this and experiment to find the best approach. My approach is just the one I found worked best for me.
That being said I do still think your approach has the fatal flaw that it makes your LikeButton component only usable once. You cannot reuse it multiple times since there is only one global copy of the state. If you render it in multiple places it will have the same state as the rest. Does that make sense?
This is slightly different from your approach where components submit their state to the root by default (or at least that's how I understand it so far).
Hrm, I don't think that's quite the approach. The root component stores the state of each component, the components don't really submit their state.
Maybe introduce subforums or tags instead? Our community isn't large enough to split it between multiple forums :-)
Categories (or tags) are on the todo list. But I'd rather keep these to something like announcement, help, community as done in the Rust forum.
I'd rather follow the Discourse approach with separate forums for each topic. Unlike the phpbb approach with sub-forums. It makes sense for Karax too, since conceptually Karax isn't affiliated with Nim (in practice it is, but let's forget that heh).
That being said I do still think your approach has the fatal flaw that it makes your LikeButton component only usable once. You cannot reuse it multiple times since there is only one global copy of the state. If you render it in multiple places it will have the same state as the rest. Does that make sense?
You're absolutely right, I totally missed that point. I also missed that components should actually have a way to modify other components' state, so overly hiding it is probably not a good idea.