slorber / scalable-frontend-with-elm-or-redux

An attempt to make Redux and Elm applications scale
http://sebastienlorber.com/
MIT License
361 stars 29 forks source link

Idea: Use 2 mailboxes/addresses/action types #2

Open slorber opened 8 years ago

slorber commented 8 years ago

Use 2 mailboxes/addresses/action types...

NewGif component has a local state, but sometimes something that happens inside this component should trigger local changes but also changes in other places of the app.

Nested actions of Elm works really great for local state, but it's hard to listen for NEW_GIF actions in other components as they are nested.

In Flux/Redux, actions are not nested, so you can easily listen for NEW_GIF actions from anywhere, but it's hard to manage a bunch of nested NewGif components without the ceremony of having to assign each of them an UUID, creating a collection/store/reducer for a list of NewGif components... Many will use this.setState() with React and only dispatch NEW_GIF when needed because it is more easy to manage.

I think we can work on combining these 2 approachs together. Decoupled components could only dispatch local actions that only themselves are aware of, and then a layer above it couple these decoupled components to our app by propagating the local event above the stack (nested event), AND dispatching a global application event (flat event).

ruiaraujo commented 8 years ago

For the nesting problem, https://github.com/erikras/multireducer works quite well.

tomkis commented 8 years ago

Using 2nd mailbox for flat events breaks fractability. To achieve fractability you need to be able to compose everything.

You will still not avoid breaking encapsulation of the component. If you want the counter to get incremented based on some domain events, you should expose public interface to increment the value, but the actual implementation of the incrementing is the implementation detail of the component. Parent component should be responsible for calling the public interface.

Listening to the "flat business event" action in the counter would make the counter aware of entire business domain -> no fractability -> no reusability -> no componentization.

slorber commented 8 years ago

@ruiaraujo great. So you can target the appropriate NewGif component by discriminating events. But is it always easy to select an appropriate key for each NewGif component? For example if I have a list of lists of NewGif, I can't use the list index as a key because there will be key collisions between those lists I think no? Can you submit a proposal with multireducer?

slorber commented 8 years ago

@tomkis1

If you want the counter to get incremented based on some domain events, you should expose public interface to increment the value, but the actual implementation of the incrementing is the implementation detail of the component. Parent component should be responsible for calling the public interface.

Yes I agree with that. The business rule should not be implemented inside the counter, but outside (because that counter could eventually be reused in some other app with a different business rule). The counter should only expose an Increment(Int) action as public API.

Listening to the "flat business event" action in the counter would make the counter aware of entire business domain -> no fractability -> no reusability -> no componentization.

The counter should not receive flat business events. But the counter could for example be wrapped by a "AppCounter" component, which will receive the the business event, and transform it to a local event for the reusable and decoupled counter.

When I'm talking about 2 mailboxes, I imagine that reusable components have a single 1, but they are wrapped by a parent that has 2. It's the responsability of the wrapper to couple that reusable component to your app, by dispatching business events.

tomkis commented 8 years ago

The counter should not receive flat business events. But the counter could for example be wrapped by a "AppCounter" component, which will receive the the business event, and transform it to a local event for the reusable and decoupled counter.

When I'm talking about 2 mailboxes, I imagine that reusable components have a single 1, but they are wrapped by a parent that has 2. It's the responsability of the wrapper to couple that reusable component to your app, by dispatching business events.

The question is though, does this approach really help us? Because you will avoid unwrapping the action based on component hierarchy but you will need to "transform it to local event" which is basically same as wrapping the action -> you need to be aware of the underlying component hierarchy.

Am I missing something?

slorber commented 8 years ago

The point is that if you decide to change the place in the DOM of AppCounter or NewGif components, you don't have to rewrite the action unwrapping stuff, while your solution here requires it.

But it's only my idea I don't have the truth :)

tomkis commented 8 years ago

Yes I understand that as mentioned above => Because you will avoid unwrapping the action

However, you will still need to "transform it to local event" (as you said) which is a reverse operation of unwrapping.

So either way, you will break the encapsulation.

tomkis commented 8 years ago

btw. @slorber if you are willing to have a closer look how this can be implemented, it's something that's possible with redux-elm as it can be seen here custom EndsWithMatcher implementation translates local Events into global.

slorber commented 8 years ago

@tomkis1 I've seen something similar where someone used some recursive code to unwrap the action chain. But can you find a way to make this work with a typed language, or is it just a hack to make it work in js only ? :)

Ie is there a typesafe way to do that? A way such that local events naming conflicts could never happen?

tomkis commented 8 years ago

Definitely not doable in Elm.

Ie is there a typesafe way to do that? A way such that local events naming conflicts could never happen?

I would say this is impossible in statically typed languages.

minedeljkovic commented 8 years ago

With addition of subscriptions in Elm 0.17, this 2nd mailbox could maybe be implemented with some sort of notification (event bus) effect manager. Then RandomGif component doing an update on NewGif action/message would issue a command like "notify anyone interested that I received a new gif". Notification effect manager would use that command to send messages to all interested subscribers. One of the subscribers would be parent of Button and Counter components, and will handle appropriate action/message as we already seen in couple of implementations here.

Now I can imagine that using effect manager for this purpose is probably going against what is stated in Elm documentation:

Together, commands and subscriptions make it possible for your Elm components to talk to the outside world

Here commands and subscriptions are used for application to talk to itself doing action/message cascading.

slorber commented 8 years ago

Thanks, I didn't know about subscriptions in Elm. Not sure to totally understand how you propose to use them but it seems to go into the same direction as a 2nd mailbox: the ability to update a deeply nested model, without having to care about intermediate/parent components