question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Confusing behaviour of emit and .withhandler

See original GitHub issue

Hello,

Thanks for creating react4s. Really liked it.

  1. I find behaviour of emit and .withhandler confusing. Documentation says Instead of updating state locally, you can choose to emit() a message to the parent component, telling it how to update the model for the inner component on this page. Also if I see emit documentation in IntelliJ I find -

/** Emit a message of type M, which can be handled by a parent component by using .withHandler(...). */ var emit : M => Unit = { _ => }

This understanding and code implemented in Tree editor example is not aligned. TreeNodeComponent emits event in renderButton method and is handled in same component (NOT parent component) in renderTodoList. Can you please explain this behaviour?

  1. Library documentation says React4s has an Elm/Redux-like emit() system . Does that mean it provide redux like store (single place to store state) and any component (not only parent component) can listen to state change??

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:17 (6 by maintainers)

github_iconTop GitHub Comments

4reactions
Ahnfeltcommented, May 17, 2018

Well, consider the case of a simple toggle button.

We can write it using a global store for the application:

case class ToggleButtonComponent() extends Component[NoEmit] {

    val global = MyGlobalStore(this)

    override def render(get : Get) =
        E.div(
            ToggleCss,
            E.div(SliderCss, SliderToggledCss.when(get(global).autosave)),
            E.div(HandleCss, HandleToggledCss.when(get(global).autosave)),
            A.onLeftClick(_ => MyGlobalStore.emit(SetAutosave(!get(global).autosave)))
        )

}

The problem is, now the toggle button mentions autosave and SetAutosave. So it can only be used to toggle whether or not the application autosaves. If I wanted another toggle button to toggle whether or not to check spelling, eg. global.spellCheck, I’d have to create a modified copy of the ToggleButtonComponent code!

If instead it’s written with props and local emit, as in the example here, I could simply use the same ToggleButtonComponent for both switches:

E.div(Text("Autosave"), Component(ToggleButtonComponent, get(autosave)).withHandler(autosave.set)),
E.div(Text("Check spelling"), Component(ToggleButtonComponent, get(spellCheck)).withHandler(spellCheck.set))
1reaction
Ahnfeltcommented, May 15, 2018

Here’s a quick implementation. I’m not an expert on Redux, so you’ll have to tell me if it lacks anything.

Implementation

This is a general class for all global stores, with messages of type M and state of type S:

abstract class GlobalStore[M, S](initialValue : S) {

    private val listeners = mutable.HashSet[Component[_]]()
    private var storedValue = initialValue

    def onEmit(message : M, currentValue : S) : S

    def emit(message : M) : Unit = {
        storedValue = onEmit(message, storedValue)
        for(listener <- listeners) listener.update()
    }

    def apply(component : Component[_]) : Signal[S] =
        component.attach(new Signal[S] with Attachable {
            listeners += component
            override def componentWillUnmount(get : Get) : Unit = listeners -= component
            override def sample(get : Get) : S = storedValue
        })
}

In short, it keeps track of who’s listening for state updates, and expects onEmit to be overriden to define what happens when a message is emitted.

Usage

Define a single concrete store for your application somewhere. Here you’ll specify how to update the state whenever a message arrives.

object VotesGlobalStore extends GlobalStore[Boolean, Int](0) {
    override def onEmit(vote : Boolean, currentValue : Int) = {
        if(vote) currentValue + 1 else currentValue - 1
    }
}

Of course, in realistic examples, the Boolean would be sum type representing the possible messages as in the tree editor example, and the state would likely be a record instead of an Int.

Once you have done this, you can access the state and emit from all your components like this:

case class VoteComponent() extends Component[NoEmit] {
    val votes = VotesGlobalStore(this)

    override def render(get : Get) = {
        E.div(
            Text(get(votes) + " votes "),
            E.button(Text("Vote up"), A.`type`("button"), A.onLeftClick(_ => VotesGlobalStore.emit(true))),
            E.button(Text("Vote down"), A.`type`("button"), A.onLeftClick(_ => VotesGlobalStore.emit(false)))
        )
    }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Reading 6.2 – Are you confused? - Talking About Behavior
One involves responding to private physiological events corresponding to “confusing” situations by labeling these events as a feeling of confusion, as taught by ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found