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

RFC: Wrapping and unwrapping actions #31

Open mattiamanzati opened 8 years ago

mattiamanzati commented 8 years ago

Not a complete solution yet, feedback is welcome! :)

https://medium.com/@MattiaManzati/how-to-reuse-redux-components-8acd5b4d376a#.53ozsapts

slorber commented 8 years ago

@mattiamanzati didn't read everything yet. About wrapping/unwrapping actions like Elm, I think there's already someone here who provided a similar implementation.

Already had the idea of having a connect() method that first allows you to select a state slice, before calling mapStateToProps. If all props to select are from the same state slice, it's possible to have memoization kick in earlier as it can detect that the state slice may not have changed, so it's not worth calling mapStateToProps.

That looks a bit similar to your selector idea, but I was more concerned about performance than ability to reuse connected components.

Ie if we have 100 connected widgets, with their state mounted in redux-store as a state slice, and the state of one widget change, how can we avoid loosing too much time on the 99th other connected widgets?

I've been thinking about this problem for a while, and it's also related to this question. No clear answer yet but it's progressing.

Was thinking, for example, that redux could for exemple allow to dispatch/subscribe to "channels" (by default a global one), so that connect() would be able to subscribe only to required channels if needed, but not sure it would be simple with current Redux contract, without breaking middlewares that batch actions... Redux only notifies of state changes, but does not include the action in that notification.

Maybe some react-redux dev has an idea? @gaearon @timdorr @jimbolla ?

Unless embrassing a full fractal architecture on your whole app (which is probably not easy in plain javascript due to boilerplate required and risky to refactor without types), not sure it's that easy to reuse connected components. For now I prefer to only reuse non-connected components, the connect function is generally not the most complicated part of an app.

Also I think it's easier to create widgets / state slices in JS, and use fractal architecture in Elm. It's possible to do both in both languages, but the language properties are not the same and an architecture may fit better in one language than another imho.

jimbolla commented 8 years ago

At first glance, my initial impression is this seems complicated. You may want to look at the source of something like Redux-form that effectively mounts multiple states into the same store.

mattiamanzati commented 8 years ago

@jimbolla Redux-Form mounts the same component multiple times if you provide it a GLOBAL unique ID to use as form name. And that's the problem; when reusing the same component you must ensure that you are using a globally unique ID. This approch, similar to some of the purposed, instead nests the state; allowing to use LOCALLY unique ID. Take this real word scenario; you have a tabbed application where you have some screens (add, remove, create, update), and each of them contains one or multiple forms. Each screen could be opened multiple times. Managing the form name becomes a mess, since you have to create a form name also based on the current tab id.

Also, Redux-Form mounts the form state at the root of the tree; imho the state of UI elements should always be tree-shaped. If you have multiple nested forms, Redux-Form will dispatch a signal when unmounting each form. This will result in multiple "UNMOUNT" actions being dispatched. When using a tree-shaped structure, the unmount can be safely be performed with a single action, that will unmount the parent state, and automatically and safely discord the entire state branch. The Redux-Form approch could lead to missing unmounts and leave dirty stuff in the state tree, which would not occour when using nested UI components states.

This approach could be seen as something complicated, but think about envelopes and postal offices. You write (dispatch) and envelope (action), with a provided address (forwardTo/wrapped action). Envelopes (actions) are received by a national postal office (the redux store), splitted by destination region and packaged (the action wrapped) to the corresponding regional office. The regional office (reducer) opens the package (unwraps the action) and splits the envelopes by destination city (the action wrapped). At the end the envelopes reaches the destianation and is read by the person (the component).

@slorber Rendering-wise the performance are already ok, beacause as pointed out by you the component re-renders only if actually necessary. At the moment the only concern is about the action iterating all over the 100 widgets reducer instead of just targeting the one wanted. This is actually wanted, before writing that code I was using something more elmish like this http://www.webpackbin.com/VkHniHP6b where the action get passed to child reducer only if it is actually targetted. Unfortunately, while this is more performant, this does not automatically manages top level events dispatchs. If the top level dispatches a ROUTE_CHANGE action, you need to pass it down manually. On the other hand, passing down all the actions except the ones targetted to another address make the API kinda more clear.

The channel idea is kinda great, the only issue is that unfortunately instead of components subscribing to channels, you'll need reducers subscribing to them. And if we want to avoid bubble down of actions, this will not prevent it.

The connect it self is not complicated, more issue are connected to side-effects managing, because most of the unpure code is done there, and therefore the target is to create some sort of "sandboxes" that could be easily boxed one inside of the other.

The global state/dispatch will be anyway still be used as a "pubsub" sistem, where you can notify everyone of something happened.

PS: Redux-Form is great, I have been working and contributing to it, but this is a global redux pattern problem ;)

jimbolla commented 8 years ago

It seems like you've got something going here. I'm just not fully grasping it.