I've built a simple TreeView with scalajs-react. Each node contains a text field.
I write some text into child 1.1
:
Now, if I add new child 1.2
below 1
, the text disappears because node 1
with all its children gets re-rendered:
When adding a child in this Javascript-Redux TreeView, the siblings are not re-rendered. How can I achieve that with scalajs-react?
See my code below or a minimal example project on GitHub.
case class Node(text: String, children: Vector[Node])
object TreeView {
val childNode = Node("1.1", Vector())
val parentNode = Node("1", Vector(childNode))
val rootNode = ScalaComponent.builder[Unit]("Node")
.initialState(parentNode)
.renderBackend[NodeBackend].build
class NodeBackend($ : BackendScope[Unit, Node]) {
def addChild =
$.modState(
_.copy(children = $.state.runNow().children :+ Node("1.2", Vector())))
def render(node: Node): VdomElement = {
val children =
if (node.children.nonEmpty)
node.children.toVdomArray(child => {
val childNode = ScalaComponent.builder[Unit]("Node")
.initialState(child)
.renderBackend[NodeBackend].build
childNode.withKey(child.text)()
})
else EmptyVdom
<.div(
node.text, <.input(), <.button("Add child", ^.onClick --> addChild),
children
)
}
}
def apply() = rootNode()
This is more along the lines of how to do it:
case class Node(label: String, text: String, children: Vector[Node])
object TreeView {
val childNode = Node("1.1", "", Vector.empty)
val parentNode = Node("1", "", Vector(childNode))
val NodeComponent = ScalaComponent.builder[Node]("Node")
.initialStateFromProps(identity)
.renderBackend[NodeBackend]
.build
class NodeBackend($: BackendScope[Node, Node]) {
def addChild =
$.modState(s =>
s.copy(children = s.children :+ Node("1.2", "", Vector.empty)))
val onTextChange: ReactEventFromInput => Callback =
_.extract(_.target.value)(t => $.modState(_.copy(text = t)))
def render(node: Node): VdomElement = {
val children =
node.children.toVdomArray(child =>
NodeComponent.withKey(child.label)(child))
val input =
<.input.text(
^.value := node.text,
^.onChange ==> onTextChange)
<.div(
node.label, input, <.button("Add child", ^.onClick --> addChild),
children
)
}
}
def root = NodeComponent(parentNode)
}
Changes
That'll do. This still concerns me because you're using stateful components which are dangerous because they're the high-level version of a big mutable variable. You might find that React makes the wrong call and throws your state away at runtime in certain situations. You can achieve the same goals in a safer way statelessly, check out https://japgolly.github.io/scalajs-react/#examples/state-snapshot for one of the ways it can be done. Hope that helps.