Giter VIP home page Giter VIP logo

nanocomponent's People

Contributors

alterx avatar bcomnes avatar callum avatar emkay avatar gmaclennan avatar goto-bus-stop avatar greenkeeper[bot] avatar greenkeeperio-bot avatar joshgillies avatar juliangruber avatar kareniel avatar lrlna avatar markgeeromano avatar perguth avatar pirxpilot avatar reminyborg avatar roobie avatar timwis avatar toddself avatar tornqvist avatar ungoldman avatar utanapishtim avatar xz64 avatar yerkopalma avatar yoshuawuyts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nanocomponent's Issues

component.use

As per our talk 2 weeks ago, it'd be useful to start thinking of component.use().

The goal of this API would be to provide a way to extend components the way we extend choo with app.use().

Not quite sure on the API yet, but got a hunch that using an internal event emitter would be the way to go for this. Input very welcome!

Image(s) not loading after navigation

I ran into the problem of images not loading after navigation.
I am not sure if this is a bug or if it's just me not understanding something correctly...

With this little app I was able to reproduce the problem:

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

class Image extends Nanocomponent {
  constructor (image) {
    super()
    this.image = image
  }

  createElement () {
    console.log(`rendering ${this.image}`)
    return html`<img src="${this.image}" alt="${this.image}" style="width: 100px;">`
  }

  update () {
    return true
  }
}

class Viewer extends Nanocomponent {
  createElement (state) {
    const container = html`<div>
   <h3>Images:</h3><p>
   <a href="/?img1=1&img2=1&img3=1">back to the overview</a>
   </p></div>`
    if(state.query.img1) 
      container.append(html`<a href="/?img1=1&img2=1"><p>1:</p>${img1.render()}</a>`)
    if(state.query.img2) 
      container.append(html`<a href="/?img2=1&img3=1"><p>2:</p>${img2.render()}</a>`)
    if(state.query.img3) 
      container.append(html`<a href="/?img3=1&img2=1"><p>3:</p>${img3.render()}</a>`)
    return container
  }

  update () {
    return true
  }
}

const img1 = new Image('1.jpg')
const img2 = new Image('2.jpg')
const img3 = new Image('3.jpg')
const viewer = new Viewer()

const app = choo()
app.mount('body')

app.route('/index.html', main)
app.route('/', main)

function main(state, emit){
  return html`<body>
    ${viewer.render(state)}
  </body>`
}

It shows multiple images, depending on the URL query.
Each image is a link that leads to another page that shows this image and one other. (with the same structure)

Reproducing the problem works in 4 steps:

  1. / - no image displayed -> click on "overview"
  2. /?img1=1&img2=1&img3=1 - all 3 images are displayed -> click on the first
  3. /?img1=1&img2=1 - image 1 and 2 are displayed -> click on the second
  4. /?img2=1&img3=1 - image 2 should be displayed, but doesn't get loaded, image 3 is ok

The html looks like the following (no src and alt property!):
<img data-nanocomponent="ncid-5f7b" id="ncid-5f7b" data-proxy="">
According to the log the component is re-rendered.
The element id stays the same (I assume this is expected).

I don't know much about the internals of choo/nanocomponent, so I am pretty helpless on my own. Thanks very much in advance!

EDIT:
I am using choo and nanocomponent only in the browser using browserify.
Versions:

"choo": "^6.13.3", 
"nanocomponent": "^6.5.2"

should onload and onunload have access to render args?

hey @yoshuawuyts, sweet module!

thinking about using this, but then i tried to apply this to a common pattern.

before using nanocomponent:

module.exports = function render (model, dispatch) {
  return html`
    <ul onload=${handleLoad}>
      ${model.cats.map(cat => html`
        <li>${cat.name}</li>
      `)}
    </ul>
  `

  function handleLoad (el) {
    dispatch(loadCats())
  }
}

while using nanocomponent:

module.exports = nanocomponent({
  render: function render (model, dispatch) {
    return html`
      <ul>
        ${model.cats.map(cat => html`
          <li>${cat.name}</li>
        `)}
      </ul>
    `
  },

  onload: function handleLoad (el) {
    // HALP, i don't have a way to dispatch actions!
    dispatch(loadCats())
  }
})

or am i missing something? how do you implement this pattern in your code?

How to manipulate choo state with nanocomponent method

Hi ๐Ÿ‘‹
I'm just starting with nanocomponent so this might be a repeated/silly question. I was wondering how can I call a method from a component that change the whole choo app state. Going further, it would be cool to share state between components and choo app, because in the createElement function I'm always passing some or all of the state.
Maybe there is an easy way to do this, but I just can figure it out.

'load' and 'unload' events are not called when using electron (node integration)

I am getting strange behavior when I use electron's built-in node integration to require nanocomponent rather than browserify.

When running the nanocomponent examples within electron, 'load' and 'unload' events are never called, even though the element is mounted to and removed from the dom. No errors are logged, and the rest of the events, including 'beforerender' all seem to behave as documented.

i don't know enough about any of the involved projects to know why there would be a difference/whether it is expected!
I can include more information + code if it is helpful =]

Document release process

Maybe add the release process to CONTRIBUTING

Update changelog date
Bump package.json
commit -m '6.4.0'
git tag v6.4.0
git push ; git push --tags
npx gh-release
npm publish

Morphing between two instances of a component leaks proxy nodes

I put together an example of nanocomponent leaking proxy elements into the real DOM when morphing between two instances of the same component:

https://gist.github.com/bcomnes/fa4fa3a83807b4189356699c17207a0b

Two issues at play here:

Want me to PR these fixes to nanocomponent?

How to use this with twitter widgets?

Following the suggestion in choojs/choo#290, I'm trying to use nanocomponent to embed a twitter widget.

First attempt:

function TwitterWidget() {
    if (!this instanceof TwitterWidget) return new TwitterWidget()
    Nanocomponent.call(this)
}

TwitterWidget.prototype = Object.create(Nanocomponent.prototype)

TwitterWidget.prototype._render = id => {
    console.log('rendering ' + id)
    this._id = id
    this._elt = html`<div></div>`
    return this._elt
}

TwitterWidget.prototype._update = newId => {
    return newId !== this._id
}

TwitterWidget.prototype._load = () => {
    console.log('loading ' + this._id)
    twttr.widgets.createTweet(this._id, this._elt)
}

const widget = new TwitterWidget()

function embedTweet(id) {
    return html`
        <div class="row">
            <div class="col-xs-12 col-md-8">
                ${widget.render(id)}
            </div>
        </div>
    `
}

This works as long as there is only one widget. To create more, I tried caching them by tweet ID:

let widgets = {}
function widgetFactory(id) {
    if (!widgets[id]) {
        console.log('creating new widget for ' + id)
        widgets[id] = new TwitterWidget()
    }
    return widgets[id].render(id)
}

function embedTweet(id) {
    return html`
        <div class="row">
            <div class="col-xs-12 col-md-8">
                ${widgetFactory(id)}
            </div>
        </div>
    `
}

Unfortunately it didn't work with two widgets because the second widget gets loaded twice. Twitter's widget code also throws an exception. Here is the console output:

bundle.js:217 creating new widget for 11111
bundle.js:198 rendering 11111
bundle.js:217 creating new widget for 22222
bundle.js:198 rendering 22222
bundle.js:210 loading 22222
bundle.js:210 loading 22222

widgets.js:9 Uncaught (in promise) TypeError: Cannot read property 'createElement' of null
    at t.createElement (widgets.js:9)
    at i (widgets.js:9)
    at widgets.js:9
(anonymous) @ widgets.js:9
i @ widgets.js:9
(anonymous) @ widgets.js:9
widgets.js:9 Uncaught (in promise) undefined
(anonymous) @ widgets.js:9
n @ widgets.js:9
widgets.js:9 Uncaught (in promise) undefined

Here is my code if you want to try recreating it: notenoughneon/nekocafe@6be5fcb
Sorry for not having a smaller test case.

How to use with sheetify? Having issue with one use case

There are two ways I use sheetify. One is css() outside of my nanocomponent and the other I'd like to use but having issue with is dynamic classes.

This is the error i get

bundle.js:1 Error: Parsing file component.js: visitor[(override || node.type)] is not a function

when I put

  createElement(obj) {
    const prefix = css`
      :host > h1 {
        text-align: center;
      }
    `;
    return html` <div  class=${prefix} onclick=${this.click}>

in my nanocomponent. I have it in my createElement before i return some html. It also has the same error if i move that prefix to the constructor. I have no problem if i consume fixed css outside

css("./component.css");
class Component extends Nanocomponent {

Using it this way works just fine. However, I'd like the flexibility of being able to use dynamically generated css through dynamic classes instead of dynamic inline styling, and I was hoping Sheetify would work for this in nanocomponent. i.e. I'd like to do

  createElement(obj) {
    const prefix = css`
      :host > h1 {
        text-align: center;
        font-size:${obj.fontSize};
      }
    `;
    return html` <div  class=${prefix} onclick=${this.click}>

disappearing list

Not sure if this is choo, nanocomponent, or nanocomponent-adapters readme issue. Or an issue with how I'm using those.

Clicking on a list item selects it as expected, but if you click an already-selected item, the list disappears, replaced with <div></div>. This is the case that I would expect the ol(items, selectedIndex, send) to recycle the cached element, b/c the arguments don't change.

RequireBin-able:

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

const li = component({
  render: function (item, index, selectedIndex, send) {
    function onClick () {
      send('select', index)
    }
    return html`
    <li onclick=${onClick}>
      ${item}
      ${index === selectedIndex ? '<-' : ''}
    </li>
    `
  }
})

const ol = component({
  render: function (items, selectedIndex, send) {
    return html`
    <ol>
      ${items.map(function (item, index) {
        return li(item, index, selectedIndex, send)
      })}
    </ol>
    `
  }
})

const app = choo()

app.model({
  state: {
    items: ['one', 'two', 'three', 'four'],
    selectedIndex: 0
  },
  reducers: {
    select: function (state, data) {
      return {selectedIndex: data}
    }
  },
})

app.router({default: '/'}, ['/', mainView])
document.body.appendChild(app.start())

function mainView (state, prev, send) {
  const {items, selectedIndex} = state
  return html`
    <section>
      ${ol(items, selectedIndex, send)}
    </section>
  `
}

Document private vars

I don't know of a good way to protect private vars from being overwritten. I know @toddself accidentally ran into the situation where he overwrote a private var. Perhaps we could add a list of private vars not to overwrite.

this._hasWindow = typeof window !== 'undefined'
this._id = null // represents the id of the root node
this._ncID = null // internal nanocomponent id
this._proxy = null
this._loaded = false // Used to debounce on-load when child-reordering
this._rootNodeName = null
this._name = name || 'nanocomponent'
this._arguments = []

this._handleLoad = this._handleLoad.bind(this)
this._handleUnload = this._handleUnload.bind(this)

Error boundries

In React 16 components introduce something new called error boundries. The main idea is that if an error is thrown from a component, it can be caught early, and prevents crashing the whole web page.

I find this interesting because:

  1. it means apps can keep running even if a component crashes (resiliency! :D)
  2. it'll play well with component.use (or similar); can send through the error to a local dev logger, or monitoring solution
  3. it's especially good to add resiliency when encapsulating 3rd party code - ideally code would never crash, but alas code isn't bug free

Thoughts?

write tests

Too much stuff is failing, we need to make sure this lib works as intended

Reference to element is wrong when morphed onto exiting DOM

I'm building an isomorphic app (using choo) where the server renders to string and the clients mounts the application on the existing DOM node. I'm actually using microcomponent but noticed an issue with the underlying mechanisms of nanocomponent. When mounting (using nanomnorph) only the attributes from the client rendered nodes are copied to the existing nodes, this has the effect that the reference to this._element in nanocomponent is not the element that is in the actual DOM, but that which was used by nanomorph for diffing. This causes any subsequent calls to morph with this._element to have no effect since they are morphing an element that is not in the DOM.

I would have made a PR but was unsure where this issue should be addressed.

Here's a requirebin that illustrates the issue (see console for output):
http://requirebin.com/?gist=b82e86737dd91c5a2c80d49e64d30684

onupdate failing

Getting the error Uncaught TypeError: CreateListFromArrayLike called on non-object (line 119) when onupdate is defined. Looking into it, but thought to drop a quick note here!

component({
  onupdate: function (el, data) {
    console.log(el, data)
  },
  render: function (data) {
    return h`<div>hello!</div>`
  }
})

Should mention this is in use w/ choo, and am running into the same issue with the example.

Render [question]

I'm creating a couple of adapters (for Custom Element API v2 and Angular (2+)) and while looking at the code of nanocomponent I could only find one place in which render is called:

if (!isRendered) {
      isRendered = true
      args = _args
      element = render.apply(render, args)
      onload(element, handleLoad, handleUnload)
}

Once this happens, it seems like it's impossible to re render the component.
Does this mean that the only way to re-render is to actually remove the node and create a new one?
In that case, why do we need the onupdate callback? It looks like there's no way to re-render the component with the updated values (if there is, I'd like to know, maybe I just missed it :D)

Of course we could just do:

onupdate: function (el, args) {
    console.log(`totally updating now`);
    this.apply(this, [args]);
},

since we call update with render as the first parameter of the apply fn. But it wouldn't really help since we can't do anything with the returned node.

I guess my question is, is there a way to re-render the same instance with the updated values, other than re creating the component?

createElement called multiple times even when update always return false

There is a decent chance I misunderstood the README, but I was expecting createElement only be called once if update always returned false, to make embedding of other DOM elements easier.

This doesn't always seem to be the case when using multiple components. Here is a test-case:

// save as app.js

var html = require('nanohtml')
var morph = require('nanomorph')
var Button = require('./component.js')

var c1 = new Component('a')
var c2 = new Component('b')

var tick = 0

// render both 10 times
for (var i = 0; i < 10; i++) {
  render(c1)
  render(c2)
}

// some render function that just updates a counter
function render (c) {
  const b = html`<body>
      ${c.render('hi:' + tick++)}
    </body>`

  morph(document.body, b)
}
// save as component.js

// This component is the one from the README in the section about mutable components.
// Modified to write out every time createElement is called.

var Nanocomponent = require('nanocomponent')
var html = require('nanohtml')
 
class Component extends Nanocomponent {
  constructor () {
    super()
    this.text = ''
  }
 
  createElement (text) {
    console.log('Calling createElement')
    this.text = text
    return html`<h1>${text}</h1>`
  }
 
  update (text) {
    if (text !== this.text) {
      this.text = text
      this.element.innerText = this.text   // Directly update the element
    }
    return false                           // Don't call createElement again
  }
 
  unload (text) {
    console.log('No longer mounted on the DOM!')
  }
}

module.exports = Component

Running the above app will result in 20 createElements being written out. If only one component is used then it's only written out once. Is that working as expected? If so how would you embed a third party dom element.

For now we've published a fork of nanomorph that supports short circuiting "guarded" elements for this using https://github.com/hyperdivision/nanomorph/blob/master/index.js#L63 and https://github.com/hyperdivision/nanomorph-guard

Thanks for all the great stuff btw! :)

allow createElement to return DocumentFragment?

nanohtml now supports document fragments, which are very handy for returning a collection of siblings without having to wrap them in an arbitrary element.

However nanocomponent is not currently designed to handle this.

If we modify the el instanceof window.Element assert in _handleRender to also accept window.DocumentFragment, we encounter an error because nanocomponent is trying to brand the returned node (which isn't a node).

This also complicates proxying and many other things. I'm not sure how much of a rewrite this would require, but I do think it would be nice to support this feature.

Thoughts?

this.rerender() undefined

Hi!

Noob here so I might just be missing something obvious or complicating things for no reason.

I am in a situation where I have to create a choo component that sets its width and height values by first loading an image as a child node, and then using that image to defines width and height of the root node. That works.

The problem comes when I add a resize EventListener and try to rerender() the component. If I log the function this.rerender() I always get undefined.

Some relevant code (file here):

load (el) {
  const resize = () => {
    if (el.firstChild.firstChild.nodeName.toLowerCase() === 'img') {
      this.width = el.firstElementChild.clientWidth
      this.height = (el.firstElementChild.firstChild.dataset.height / el.firstElementChild.firstChild.dataset.width) * el.firstElementChild.firstChild.clientWidth
    } else {
      this.width = el.firstElementChild.firstChild.clientWidth
      this.height = (el.firstElementChild.firstChild.firstChild.dataset.height / el.firstElementChild.firstChild.firstChild.dataset.width) * el.firstElementChild.firstChild.firstChild.clientWidth
    }

    this.rerender.bind(this)()
    console.log( this.rerender.bind(this)() )
}

resize()

window.addEventListener('resize', function () {
  resize()
})

}

update (el) {
  return true
}

and how I create the component (file here):

function entry (state, emit) {
  return ov(page[0].children).map(function (item, i) {

  var card = state.cache(Card, i)
    return card.render(state, emit, item, i, state.cards[i])
  })
}

Could it be that state.cache does not see that I am changing the width and height values when resizing the page, so... I should be explicit about that?

Thanks,
af

How to get nanocomponent instance reference from root node?

Hi ๐Ÿ‘‹

I have a situation where I have many components and some external libraries which manipulates the DOM. So, those libraries transform the root node of my components, and I need to run functions from the components after they are manipulated but I can't figure out how to get reference to that instance from the node manipulated for this library.

In other words, what I would like is the inverse of the element property of nanocomponent, perhaps a function that given a DOM element returns the actual nanocomponent tied to it. Is this possible?

Thanks :)

async data loading

Apologies for the rough draft here; I didn't have much time to phrase things here properly, but I hope the general idea makes sense. This is mostly so we can start thinking about this problem, and create a concrete starting point to work from.


Some of the more CMS-y applications make heavy use of server rendering, which produces pages that can then be stored on CDNs. In order for components to be rendered on the server, it might be useful if they could define an initial set of data before being rendered.

There's probably a good set of considerations as to the more finer grained details, but I was thinking an API like this might just be the right fit.

// button.js
var Nanocomponent = require('nanocomponent')
var html = require('bel')

function Button () {
  if (!(this instanceof Button)) return new Button()
  this.color = null
  Nanocomponent.call(this)
}
Button.prototype = Object.create(Nanocomponent.prototype)

// This the new API.
Button.prototype.initialState = function (done) {
  var self = this
  xhr('/foo/bar', function (err, data) {
    self.state.data = data
    done()
  })
}

Button.prototype.createElement = function (color) {
  this.color = color
  return html`
    <button style="background-color: ${color}">
      Click Me
    </button>
  `
}

// Implement conditional rendering
Button.prototype.update = function (newColor) {
  return newColor !== this.color
}

In Node, we could wait for all these functions to resolve, before doing a final render. It's a lil messy, but might be the best we got. Thanks & thoughts very welcome! ๐Ÿ˜

Question about lifecycle, internal component state

Hello,

I'm wondering about how one would trigger a render from from inside a component mounted inside a choo app.

I'm referencing your leaflet component and the lifecycle diagram, and want to take a swing at a google maps one, but I'm having trouble figuring out how to emulate React's setState. Choo is cool because you have absolute control about when to rerender, but I can't wrap my head around how that gels with the ability of a component to trigger it's own render based on setState.

It seems that if I want render to be called on my component, I would have to pass in the emit fn? Is that the case, or is there a way I can force a render without any upstream dependencies?

Thanks for any help you could provide!

collections?

Am I missing something? If I change the choo example to have 2 buttons

var component = require('nanocomponent')
var html = require('choo/html')
var choo = require('choo')

// create new nanocomponent
var customButton = component({
  render: function (data) {
    return html`
      <button>${data}</button>
    `
  }
})

var app = choo()
choo.router(['/', mainView])
document.body.appendChild(choo.start())

mainView (state, prev, send) {
  return html`
    <section>
      ${customButton(state.one)}
      ${customButton(state.two)}
    </section>
  `
}

it doesn't really cache those elements when state.one and state.two change, right?

this.element is null while server rendering?

I've made a nanocomponent, but this.element is undefined while server rendering, and it makes bankai stop when I try to do anything with it, in my case calling getElementsByClassName().

As you can see int the screeshot, it works fine on the browser. Any ideas? Am I doing something wrong, or could this be an issue?

image

(I'm using an example from Bootstrap as template)

test-choojs.zip

`isMounted` check breaks iteration

This issue may apply more broadly to the way nanocomponent manages component instances.

However, in the below example the resulting list contains one item, as opposed to the expected three:

const component = require('nanocomponent')
const html = require('bel')

const li = component({
  render: function (item, index) {
    return html`
    <li>${item}</li>
    `
  }
})

const ol = component({
  render: function (items) {
    return html`
    <ol>
      ${items.map(function (item, index) {
        return li(item, index)
      })}
    </ol>
    `
  }
})

const state = {
  items: ['one', 'two', 'three']
}

function mainView (state) {
  const { items } = state
  return html`
    <section>
      ${ol(items)}
    </section>
  `
}

document.body.appendChild(mainView(state))

My hunch here is that the isMounted check, is causing the issue.

calling this.render inside beforender causes nanocomponent to not unmount when necessary

I've experienced an interesting bug where I need to use beforerender to make an xhr call to get some data, and change my component based on the data i get in xhr call, after the call is complete, i have been calling this.render() to re-render the element with the new data. This is an example of my component:

PreviewComponent.prototype.createElement = function (state, emit) {
  this.state = state
  this.emit = emit

  return html`
    <div class="container">
      <div id="editor-container" class="row">
        ${this.preview}
      </div>
    </div>
  `
}

PreviewComponent.prototype.beforerender = function () {
  // make the xhr request and get data back
  this._getScript((data) => {
    const html = document.createElement('div')
    html.innerHTML = data.html

    this.preview = html
    // this bugs out!
    this.render(this.state, this.emit)
  })
}

I was trying to log see the component on create, and what would happen when i theoretically would unload the component. The component doesn't unload, but does come back with the element being empty. i.e.:
screen shot 2017-08-30 at 19 48 55

I am currently just hacking around it and instead of render just calling a set timeout, which helps with unmountinng the component for now; something like this:

setTimeout(function () {
  // render
}, 0)

I would make a PR but I am still not sure how to fix it, if anyone knows how, lmk!

Thanks ^__^

Using `placeholder` breaks `onupdate` - unable to update and modify DOM element.

The use of a placeholder because it uses nanomorph under the hood, means that the element that gets passed to the first argument f onupdate is actually NOT the one that gets inserted into the document and returned by the first render. Thus, any attempt to modify that element has no impact on the actual DOM element.

The root cause is that due to using polite-element the actual element that gets inserted is the placeholder element. Then the rendered element is used to diff and update the placeholder element. Thus the placeholder DOM element is the one that needs to be returned and provided to the onupdate element and any other place that the element is passed in.

Work around: Cache the element that gets passed into onload (this seems to be the right one) and use this in onupdate, or don't use a placeholder element.

Here's a test case that shows the behaviour.

var component = require('nanocomponent');
var html = require('bel');
var assert = require('assert');

var el;
var comp = component({
  onupdate: function (e, name) {
    // This e refers to the element that was used by nanomorph in polite-component to diff
    // and update the placeholder element
    e.textContent = 'Updated, ' + name;

    // This line will work and update the element:
    // el.textContent = 'Updated, ' + name;

    // This will throw, as the elements are not equal
    assert.equal(e, el, 'Element should be equal');
  },
  placeholder: function (name) {
    return html`
      <div>Placeholder, ${name}</div>
    `;
  },
  render: function render(name) {
    return html`
      <div>Hello, ${name}</div>
    `
  }
})

// Render "Placeholder, Mike" and then "Hello, Mike"
el = comp('Mike');

// Try to set the component to 'Updated, sarah' after 1 second
setTimeout(() => {
  comp('Sarah');
}, 1000);

document.body.appendChild(el);

Can't handle svg

I have a simple component that should render an svg. But nanocomponent throws

Uncaught Error: nanocomponent: createElement should return a DOM node
    at assert (index.js:21)
    at Block.Nanocomponent._handleRender (index.js:93)
    at Block.Nanocomponent.render (index.js:71)
    at Object.values.map.block (blockPanel.js:33)
    at Array.map (<anonymous>)
    at BlockPanel.createElement (blockPanel.js:33)
    at BlockPanel.Nanocomponent._handleRender (index.js:90)
    at BlockPanel.Nanocomponent.render (index.js:71)
    at Choo.mainView [as _handler] (index.js:21)
    at Choo._prerender (index.js:244)

that's because this line fails. I think this assertion should be fixed to include svg (and actually everything that can be handled by bel itself)

caching issue

Trying to use a component multiple times but it seems like the first rendered element is returned on subsequent calls. Is this intended?

index.js

var component = require('nanocomponent')
var html = require('bel')

var button = component({
  render: function (text) {
    return html`
      <button>${text}</button>
    `
  }
})

button('one') == button('two') // => true

package.json

{
  "dependencies": {
    "bel": "^4.5.1",
    "nanocomponent": "^2.0.0"
  }
}

Clarify intent of `onupdate` lifecycle event

Judging by nanocomponent-adapters/react my initial impression was that after calling a nanocomponents render function, the returned element can be called to trigger component updates, and additionally even re-render the component.

However, in both my testing and in reviewing the source code here it seems as thought this isn't the case. In fact does this comment more accurately describes the overall intent?

So to clarify is it up to an author of nanocomponent components to decide how to best manage updates/renders of a component eg. using nanomorph or morphdom or something else entirely via a components onupdate handler? Also, should the above example in nanocomponent-adapters/react be considered a POC and not reflective of the actual implementation or intent of the nanocomponent API?

Road to 6.0.0

Here is the remaining work from the initial 6.0.0-0 beta PR:

  • Add browser tests (https://gist.github.com/bcomnes/fa4fa3a83807b4189356699c17207a0b)
  • Make examples runnable (component playground? perhaps put this off or use that thing @toddself said)
  • Refine example: we could remove the ES6 example, and put it together with the constructor bind under the same heading
  • Have a few ๐Ÿ‘€ look over the CI setup to make sure everything is groovy
  • Update "How does it work?" to describe the lifecycle.

document this.element

this.element is only briefly mentioned in docs, but is a pretty handy and key piece of the nanocomponent API. Would be good to document for users.

Call update on render when component is not in DOM

I was playing around with nanocomponent and component-box as possible target solution for jsx compilation and stumbled upon an issue.
Sometimes we may expect updaing a component state by calling render, even when the component is not in the DOM, eg.

let menu = new Menu()

// these render calls do not incur update call
menu.render({ content: 'Home' })
menu.render({ content: ['A', 'B'] })

document.body.appendChild(menu.render())

Does it make any sense to invoke update even for detached components? We can imagine some components not dealing with DOM at all.

Does the readme example work?

// Implementer API
var Nanocomponent = require('nanocomponent')
var html = require('bel')

function MyButton () {
  if (!(this instanceof MyButton)) return new MyButton()
  this._color = null
  Nanocomponent.call(this)
}
MyButton.prototype = Object.create(Nanocomponent.prototype)

MyButton.prototype._render = function (color) {
  this._color = color
  return html`
    <button style="background-color: ${color}">
      Click Me
    </button>
  `
}

MyButton.prototype._update = function (newColor) {
  return newColor !== this._color
}

I might be going nuts here, but does the readme example actually work? On subsequent calls to render, you would have to mutate this._element with the new color right? See https://github.com/yoshuawuyts/nanocomponent/blob/master/index.js#L48

Since the render function only returns an element, it would only get captured on the first render() call.

I'll double check this in a moment.

leaky proxy nodes when morphing

Not sure if we want this issue here or in nanomorph, but looks like proxy nodes are leaked when a component is morphed to a new location in the dom. For example, if these two views are morphed, a proxy node will be rendered rather than the component:

function viewA (state, emit) {
  return html`
    <body>
      <a href="/">beep</a>
      <a href="/boop">boop</a>
      <div>
        ${component.render()}
      </div>
    </body>
  `
}

function viewB (state, emit) {
  return html`
    <body>
      <a href="/">beep</a>
      <a href="/boop">boop</a>
      ${component.render()}
    </body>
  `
}

You can see this behavior here:

Also interesting to note if the component's update function returns true, the component will leak the proxy node on first morph, while subsequent morphs will correctly return the component.

API method names

Hey there!

I am coming to the Choo universe (chooniverse?) from React and so far I like the lighter-weight approach. There were some confusing things about the Nanocomponent API that held me up a bit. I'm curious about the reasoning behind the API method names.

  1. Why isn't update() called something like shouldUpdate()? As a user, I would expect the component to re-render if I call this.update()
  2. Why are the lifecycle methods all lowercase, but don't use the on prefix? Is there a precedent for this? Same issue as above, where I would expect load() and unload() to do something different. Maybe it would make more sense to use mount / unmount since those terms are used elsewhere in the Choo documentation?

These questions may have simple answers but they weren't obvious to me.
Thanks for all your work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.