calmm-js / karet

Karet is a library that allows you to embed Kefir observables into React VDOM
MIT License
83 stars 8 forks source link

karet and lists of children #19

Closed mAAdhaTTah closed 6 years ago

mAAdhaTTah commented 6 years ago

I'm working on a framework of my own, borrowing some ideas from karet, and I'm struggling with iterated lists, specifically the key attribute. What I'd like to be able to do is structure a list something like this:

const TodoList = ({ props$ }) => (
    <div>
        <h2>My Todos</h2>
        {props$.map(todos => todos.map(todo => (
            <TodoItem props$={/* how do i get a stream representing an individual todo? */}
                      key={todo.id} />
        )))}
    </div>
)

As the comment suggests, I'd like to provide the child component a stream representing the current state of the individual list item, but I'm not seeing a good way to structure the todos object that doesn't end up requiring a lot of iteration to create a stream of todo instances.

Based on the documentation, it appears you're mostly using the array indicies with lenses to decompose (? is this the right word?) the central Atom into into individual streams for each instance. The problem I'm having with that is using the array index as the key is a React anti-pattern. While the linked post suggests it could produce strange or broken results if you add or remove elements from the array, in my use case, it would also prevent the VDOM implementation from rearranging the currently existing DOM nodes and produce node updates instead. Let me explain:

If we have a list that looks like this:

<h1>My Favorite View Libraries</h1>
<ol>
    <li>React</li>
    <li>karet</li>
    <li>Vue.js</li>
</ol>

And we decide to move karet to the first spot to make it look like this:

<h1>My Favorite View Libraries</h1>
<ol>
    <li>karet</li>
    <li>React</li>
    <li>Vue.js</li>
</ol>

using array indexes would produce 2 text node changes, rather than rearranging the current DOM. This is inefficient, as a single move would be better than 2 text changes, and it denies us the ability to animate the DOM movement, as the DOM node doesn't actually change position.

I've played around with karet and I love the idea of being able to embed observables into the VDOM like that, and I'm trying to take it a bit further, such that all possible interactions in the DOM are expressed as embedded Observables, including DOM attributes & events, but I'm struggling with the API design around children, and I'm wondering how you solve this with karet / kefir.atom.

My current thought is to require something like this:

const TodoList = ({ props$ }) => (
    <div>
        <h2>My Todos</h2>
        {props$.map(todos => todos.order.map(key => (
            <TodoItem props$={props$.map(todos => todos.dict[key])}
                      key={key} />
        )))}
    </div>
)

Where todos looks like this:

const todos = {
    order: ['a', 'c', 'b'],
    dict: {
        a: todo1,
        b: todo2,
        c: todo3
    }
}

Is this a pattern you've used before with karet? Do you have any insight / suggestions for this API design?

Thanks for your time!

polytypic commented 6 years ago

What I'd like to be able to do is structure a list something like this: [...]

The Karet Util library provides the mapElemsWithIds function for that purpose:

const TodoList = ({ props$ }) => (
    <div>
        <h2>My Todos</h2>
        {mapElemsWithIds(
             'id',
             (props$, id) => <TodoItem props$={props$} key={id}/>,
             props$
         )}
    </div>
)

Based on the documentation, it appears you're mostly using the array indicies with lenses to decompose (? is this the right word?) the central Atom into into individual streams for each instance.

While the written documentation isn't incorrect, it unfortunately has not been kept fully up to date with the libraries and other things we've learned along the way. If you are interested in learning more about Calmm, I recommend joining the Gitter channel and asking questions.

The problem I'm having with that is using the array index as the key is a React anti-pattern. While the linked post suggests it could produce strange or broken results if you add or remove elements from the array, in my use case, it would also prevent the VDOM implementation from rearranging the currently existing DOM nodes and produce node updates instead.

Yes, if you have stateful elements, then you indeed need to setup identities for them or otherwise React will not be able to correctly associate the elements with their DOM. And, as you say, identities can also improve performance with stateless elements, but with stateless elements identities are not required for correctness as the end result will be the same.

My current thought is to require something like this: [...] Is this a pattern you've used before with karet?

The pattern is familiar to me, but I don't think I've used it.

Do you have any insight / suggestions for this API design?

Yes, but no time to comment right now. I'll try to get back to this a bit later.