choo icon indicating copy to clipboard operation
choo copied to clipboard

Documentation for how to handle DOM refs within a component/view

Open nilliams opened this issue 8 years ago • 3 comments

Not sure if just a docs issue, but seems to be taking a lot of figuring out, thanks! Sorry if I've missed something obvious.

Expected behavior

Docs to show me how to get a reference to one dom node from another in same view, e.g. access the value of an input, in the onclick handler of a button in the same form. (Like a refs callback in React).

Actual behavior

Can't find an example of this. In the todo example (as it uses enter key), in the handbook, or in bel docs. Tried using onload but that only works once, see below.

Steps to reproduce behavior

Attempt using onload which only works first time we add (presumably DOM node gets blown away on next render), NOTE: you will have to go to the package.json tab and change choo version to 5.4.0, it refuses to save: http://requirebin.com/?gist=02f05c009e84bb7cbeeed4e62d095b42

nilliams avatar May 07 '17 11:05 nilliams

The load event will only fire if the element is replaced by nanomorph. It works the first time because the onload handler assigns a value to input, then on subsequent renders input gets re-initialized but the handler never fires (because the new input is never added to the document).

I think you could move let input up out of the function scope to combat this, but it's not a good habit.

If a render came down (from another store or whatever) while the user was typing, their input might get blown away. You're better off storing all your state in state (as in the choo example). You could wire up your input like this:

<input type="text" value=${state.newLabel} oninput=${(ev) => emit('input', ev.target.value)} />

Handle the input event in your store:

function todoStore(state, emitter) {
  state.newLabel = ''
  state.todos = [{
    label: 'Do a thing',
  }]

  emitter.on('input', (val) => {
    state.newLabel = val
  })
  
  emitter.on('add', () => {
    state.todos.push({ label: state.newLabel })
    state.newLabel = ''
    emitter.emit('render')
  })
}

This way the new label value is in state immediately and if a render occurs at any time, the dom will still reflect the state of the input.

lvivier avatar May 17 '17 08:05 lvivier

^ this is what I'd do too

yoshuawuyts avatar May 17 '17 09:05 yoshuawuyts

Thanks, and thanks for the clear explanation @lvivier

Kinda as I thought by looking at the choo example, and playing with onload() more, though having to wire up intermittent state that I don't actually care about is pretty unfortunate, and what React manages to avoid with their ref callback.

(Here's one of the clearest examples of using ref in a stateless functional component for ... reference).

I assume choo/bel would need to add a callback similar to ref that gets called every single render to get similar functionality. Any thoughts on adding this and/or whether the docs should cover the use-case regardless?

nilliams avatar May 17 '17 11:05 nilliams