anthonyshort / deku

Render interfaces using pure functions and virtual DOM
https://github.com/anthonyshort/deku/tree/master/docs
3.41k stars 130 forks source link

2.0.0 #299

Closed anthonyshort closed 8 years ago

anthonyshort commented 8 years ago

The next version of Deku is nearly done. It has been rewritten to clean up a lot of the code which has made it much easier to test. The API is smaller and doesn't include a lot of the React-like features that we don't really need. The biggest change is that it will have tighter integration with Redux and rely on that for managing state and actions.

I've been experimenting with two directions. We either:

  1. Make the Elm Architecture the default way to manage state and actions
  2. Have tighter integration with Redux and use it instead

Either way, there's no reason we need to have state stored at all within Deku. We're moving that to userland.

Tasks

kvnneff commented 8 years ago

Say you're working with a 3rd party lib such as leaflet where you're initializing a class that you might want to pass down to other components via props. In the app state you only want plain objects, so you can't send it to Redux.

What I'm doing now is creating the class in the afterMount method, adding it to the component's state via setState and then in the render method I'm grabbing it from the component state and passing it down through props. I'm curious how you guys are handling this when you no longer have component state and you've only got app state.

anthonyshort commented 8 years ago

In the app state you only want plain objects

I think in general as long as you can recompute the state from previous actions it's not horrible.

However, you could also just have some stateful middleware for using Leaflet or other third-party libs. Fire an action after mount with the id of the map element (you could use path for this to ensure it's unique and deterministic), the middleware listens for that and creates the map on the element. On before unmount you can clean up the map by firing another action. You could avoid needing to pass it down at all, and just pass a reference id that child components can use to emit further actions.

Then you're treating it like any other external, stateful resource. Here's a super rough idea:

// leaflet-middlware
const maps = {}

export const actions = {
   // actions here
}

export const leaflet = store => next => action => {
  if (!action.type.startsWith('LEAFLET_')) return next(action)
  let { mapId } = action
  let map = maps[mapId]

  switch (action.type) {
    'LEAFLET_CREATE': 
       maps[mapId] = L.map('mapId')
       break
    'LEAFLET_SETVIEW': 
        map.setView(action.coords, action.zoom)
        break
     'LEAFLET_MARKER':
        L.marker(action.coords)
          .addTo(map)
          .bindPopup(action.text)
          .openPopup()
  }
}

It's not great, but since it's dealing with this stateful map object, my first thought would be to stuff it into some middleware and manage it there. That way your component can still be easily tested since it's just strings and actions. The Leaflet middleware is still difficult to test since it's storing internal state, but it's easier than trying to test it within components and coupling the two tests together.

kvnneff commented 8 years ago

Thanks for the detailed explanation, that makes total sense to me!

JoeLanglois commented 8 years ago

No local state for components? Redux wasn't meant to deal with ALL state, it was meant to hold important application state.

Trivial view state such as "viewTooltip" for example shouldn't clutter the redux state tree.

Make deku components stateless and they become no better than functions which actually compose much better than components.

Am I missing something?

Can we let the user choose if he wants to use local state or not like pre-2.0?

anthonyshort commented 8 years ago

@JoeLanglois It's just up to the user to decide how to store state and deal with it. You can use things like redux-ephemeral or you can store it in a different object and pass it in along with redux state.

Either way the UI just communicates with the outside world through actions, something like dispatch(updateState(path, state)) would work the same way.

Make deku components stateless and they become no better than functions which actually compose much better than components.

Almost. You don't even need to really use components, you can just use functions. Components are lazily-rendered thunks (although you can just memoize a function too), you can use lifecycle hooks to perform side-effects, and you'll get a path you can use to uniquely identify the component (for storing state).

anthonyshort commented 8 years ago

^ It also lets you use the elm architecture if you want to store state. There's no real reason you wouldn't store all state in a single atom if it's separately correctly.

JoeLanglois commented 8 years ago

@anthonyshort, thanks for your response, I have been testing this "pure-view" architecture for a couple hours and it's true that it simplifies things A LOT.

The Elm Architecture makes things so easy/elegant. Can't wait to be able to use 2.0. Any release date?

anthonyshort commented 8 years ago

@JoeLanglois Probably tomorrow. The docs won't be completely done by then, but the API is pretty small and usable. The functionality is all done and tested though.

ashaffer commented 8 years ago

This is looking awesome. Really like how simple everything has become.

JoeLanglois commented 8 years ago

Great! Can't wait to try it out

kesla commented 8 years ago

:dancers:

ashaffer commented 8 years ago

@anthonyshort Out of curiosity, why'd you decide to go with imperative dispatch over returning actions from event handlers / lifecycle hooks? Any particular reasoning there?

Also, have you considered allowing at the top-level some sort of component wrapper? Something that would allow you to do something equivalent to compose(fn, component) for all components. Via the intersection of that and context, you could implement something analogous to virtex-local (without all of the deep redux integration i've been doing) if you wanted to.

anthonyshort commented 8 years ago

@ashaffer I really liked that approach, but I just thought it coupled it too much with the concept of actions so the learning curve would be higher. I could be wrong, and maybe we'll end up doing it that way too. I think there was a bit of complexity around getting event handlers to work.

That wrapper idea sounds awesome. It could be an option for createRenderer. The only problem would be that it could create hidden dependencies for components that are relying on some wrapper existing at the top level. That's the main reason I didn't like the snabbdom approach to plugins.

You could get something very similar to virtex-local by just composing your render function or component. I'd love to get local state to work that way with just a wrapper instead of embedding it into Deku.