justin-schroeder / arrow-js

Reactivity without the framework

Home Page:https://arrow-js.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add documentation for keys (list rendering)

nbgraham opened this issue · comments

If you have a list of data elements and then change the order of them, the DOM gets out of sync and can render more elements than actually exist in the data.

I wrote a simple todo app with the ability to mark a todo as done. Done todos are sorted to the bottom.

I only had four total todos and marked them done in an odd order. Once I marked them all as done, I got duplicate DOM elements leading to 6 list items, even though the data list still only had 4 items.
This doesn't alway happen at the same time, but it does happen consistently.

image

I think a "key" is needed to track identical DOM elements across renders, like React.

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

type Todo = {
  label: string;
  isDone: boolean;
};

const App = () => {
  const data = r({
    todos: [] as Todo[],
  });

  w(() => console.log("todos changes", JSON.stringify(data.todos)));

  const addTodo = (label: string) => {
    console.log("add todo", label);
    const newTodo: Todo = { label, isDone: false };
    data.todos = [...data.todos, newTodo] as any;
  };

  const markDone = (todo: Todo) => () => {
    console.log("mark done", todo);
    data.todos = data.todos.map((t) =>
      t === todo ? { ...t, isDone: true } : t
    ) as any;
  };

  const sortedTodos = () => {
    return [...data.todos].sort((a, b) => {
      if (a.isDone === b.isDone) {
        return 0;
      }
      return a.isDone ? 1 : -1;
    });
  };

  return t`
        <h1>Todo List</h1>
        <input type="text" @keyup="${(e: KeyboardEvent) => {
          if (e.code === "Enter") {
            const target = e.target as HTMLInputElement;
            addTodo(target.value);
            target.value = "";
          }
        }}">
        <ul>
        ${() =>
          sortedTodos().map((todo) =>
            TodoItem({ todo, markDone: markDone(todo) })
          )}
        </ul>
        `;
};

const TodoItem = ({ todo, markDone }: { todo: Todo; markDone: () => void }) => {
  const style = () => {
    if (todo.isDone) {
      return `text-decoration: line-through`;
    }
    return "";
  };

  return t`
        <li style=${style}><button @click=${markDone}>Done</button>${todo.label}</li>
    `;
};

const appElement = document.getElementById("app");
App()(appElement);

Ahh, this is actually in there, it just isn't properly documented.

t`<li>item</li>`.key(yourKey)

Ah gotcha, that worked for me, thanks!
Do you want me to close this or keep it open to track the need for documentation?

Ok docs have been added for this 👍