Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] add thunking API #40

Open
yoshuawuyts opened this issue Aug 3, 2016 · 7 comments
Open

[feature] add thunking API #40

yoshuawuyts opened this issue Aug 3, 2016 · 7 comments

Comments

@yoshuawuyts
Copy link
Member

In yo-yo and choo it's a best practice to have element functions in an
application return the same element given the same input. This mechanism is
commonly referred to as thunking.

How would we feel about adding a light wrapper that exposes a thunk function
so that elements can be thunked with little effort. I was thinking of an API
along these lines:

// elements/my-button.js
const thunk = require('bel/thunk')
const html = require('bel')

module.exports = thunk(createMyButton)

// create a cool button that has a different color
function createMyButton (color) {
  return html`
    <button style="background-color: ${color}">
      Click me! :D
    </button>
  `
}

In a choo app this could then be consumed as:

const html = require('choo/html')
const choo = require('choo')

const myButton = require('./elements/my-button')

const app = choo()
app.router([ '/', myView ])

app.model({
  state: { color: 'blue' }
})

const tree = app.start()
document.body.appendChild(tree)

function myView (state, prev, send) {
  return html`
    <main>
      ${myButton(state.color)}
    </main>
  `
}

What do you think? Would this be a useful addition to bel, or should we
continue to point people to use an external thing / write it themselves? I was
thinking of including this into choo, but given that we strongly encourage
people to use bel for standalone components (yay, reusability) I figured it
might make more sense to include it here first, and then have it propagate
upwards through yo-yo to choo. Thanks!

See Also

@shama
Copy link
Member

shama commented Aug 3, 2016

Hmm let me give it some more thought. Initially it feels like this should be in the diffing library or yo-yo as bel doesn't know about previous elements.

@timwis
Copy link
Member

timwis commented Aug 4, 2016

Yeah, doesn't thunking only become relevant when you're executing the element creation over and over again (a la morphdom)?

@yoshuawuyts
Copy link
Member Author

@timwis yeah you're right, but this type of rendering is quickly becoming the default; e.g.

  • react
  • yo-yo, choo
  • ember
  • mithril, mercury, etc.

Probably the only hot new one around that doesn't do virtual-dom diffing would be Angular 2, but that's about it.

@shama
Copy link
Member

shama commented Aug 9, 2016

Just an update that I've been experimenting with ideas but nothing really to show for yet. But wanted to mention using a feature like this for handling <canvas> updates.

Currently <canvas> tags don't work well in the idiomatic flow because the tag never indicates itself as changed. The simple fix would be to always assume a <canvas> has changed when morphing, replacing with the new one.

But it would interesting if we let the element author choose when to invalidate. Basically the thunk just remembers the last passed arguments/element and appends to the function. Then any element not marked or first time through the thunk, gets updated. Any previous that are marked will always be ignored:

const html = require('yo-yo')
const thunk = require('yo-yo/thunk')

/* prev arg gets added by thunk to the end of the function call */
module.exports = thunk(function createCanvas (data, prev) {
  let canvas = prev.element
  if (prev.args[0].width !== data.width || prev.args[0].height !== data.height) {
    canvas = html`<canvas width="${data.width}" height="${data.height}" />`
  }
  const ctx = canvas.getContext('2d')
  ctx.fillRect(data.x, data.y, data.width, data.height)
  return canvas
})

/* ... */

let data = {x:0,y:0,width:100,height:100}
raf(function loop () {
  data.x += .1
  if (data.x > 100) data.width *= 2
  yo.update(root, canvas(data))
  raf(loop)
})

This might simplify the validation check too as we wouldn't need to traverse objects and arrays checking if the arguments have changed. We just let the element author choose the parameters that decide that. Which most of the time would be 1 or 2 if statements.

Or we could provide that API for convenience to the author to apply as needed:

const html = require('yo-yo')
const thunk = require('yo-yo/thunk')
module.exports = thunk(function createMyButton (data, prev) {
  let button = prev.element
  if (!button || prev.hasChanged(data)) {
    button = html`<button>${data.label}</button>`
  }
  return button
})

@timwis
Copy link
Member

timwis commented Aug 9, 2016

That looks awesome @shama! Thanks for the writeup. prev being an object with element and args properties (rather than just the previous version of data) was confusing at first but I don't have a better idea. Maybe if it were called cache or something...

If I recall correctly, the way react (or at least redux) handles this is by "mapping state to props" when connecting the store to the component, so that react knows whether to update that specific component on a given state change. Right?

@roobie
Copy link

roobie commented Feb 6, 2017

@yoshuawuyts https://github.com/yoshuawuyts/cache-element/ can be used to thunk element, or am I confused?

@yoshuawuyts
Copy link
Member Author

yoshuawuyts commented Feb 7, 2017 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants