justin-schroeder / arrow-js

Reactivity without the framework
https://arrow-js.com
MIT License
2.41k stars 49 forks source link

Keyed lists causing re-render #24

Closed Grafikart closed 1 year ago

Grafikart commented 1 year ago

I have just tried the library and it's quite impressive for a such small code but I'm having an issue with list and map. We can use .key() to avoid creating new item but the problem happens when we use

  return t`
      <ul>
        ${() => state.todos.map((todo) => TodoItem(todo).key(todo.id) )}
      </ul>
  `;

The problem with this loop, it will rerender every list item even if we only edit one element todos[1].title for instance.

Expectation

The system could proxy the map méthod to "memo" the value and only rerun the method when the item in the array changed.

justin-schroeder commented 1 year ago

This depends on how your TodoItem is structured. If it renders its props also using arrow fns, those should be isolating boundaries preventing the list from re-rendering.

It is still quite experimental however so if it’s not actually doing that properly it wouldn’t be a huge shock, are you able to provide a reproduction?

Grafikart commented 1 year ago

Here is an example of the problem (I just kept the code linked to the issue)

import { r, t} from "@arrow-js/core";

const root = document.querySelector("#root");
const endpoint = "http://jsonplaceholder.typicode.com/todos?_limit=10";

const TodoItem = (todo) => {
  return t`<li>
    <span>${() => todo.title}</span>
    <input type="text" name="title" value="${todo.title}" @input="${(e) => todo.title = e.currentTarget.value}">
  </li> `;
};

const TodoList = () => {
  const state = r({
    loading: true,
    todos: [],
  })

  fetch(endpoint).then(r => r.json()).then(todos => {
    state.loading = false
    state.todos = todos
  })

  return t`
    <div>
      ${() => state.loading && t`<div>Chargement...</div>`}
      <ul>
        ${() => state.todos.map((todo) => TodoItem(todo).key(todo.id))}
      </ul>
    </div>
  `;
};

TodoList()(root);

I created a CodeSandbox https://codesandbox.io/s/arrow-js-list-hej0sn?file=/src/index.js to illustrate the problem. When I input a letter in an input, the whole list is rerendered and we loose the focus.

justin-schroeder commented 1 year ago

Hmm..interesting. It seems this is related to using keys specifically — If I remove the keys, all good:

https://codesandbox.io/s/arrow-js-list-forked-vimmge?file=/src/index.js