I have a newbie question on SolidJS. I have an array with objects, like a to-do list. I render this as a list with input fields to edit one of the properties in these objects. When typing in one of the input fields, the input directly loses focus though.
How can I prevent the inputs to lose focus when typing?
Here is a CodeSandbox example demonstrating the issue: https://codesandbox.io/s/6s8y2x?file=/src/main.tsx
Here is the source code demonstrating the issue:
import { render } from "solid-js/web";
import { createSignal, For } from 'solid-js'
function App() {
const [todos, setTodos] = createSignal([
{ id: 1, text: 'cleanup' },
{ id: 2, text: 'groceries' },
])
return (
<div>
<div>
<h2>Todos</h2>
<p>
Problem: whilst typing in one of the input fields, they lose focus
</p>
<For each={todos()}>
{(todo, index) => {
console.log('render', index(), todo)
return <div>
<input
value={todo.text}
onInput={event => {
setTodos(todos => {
return replace(todos, index(), {
...todo,
text: event.target.value
})
})
}}
/>
</div>
}}
</For>
Data: {JSON.stringify(todos())}
</div>
</div>
);
}
/*
* Returns a cloned array where the item at the provided index is replaced
*/
function replace<T>(array: Array<T>, index: number, newItem: T) : Array<T> {
const clone = array.slice(0)
clone[index] = newItem
return clone
}
render(() => <App />, document.getElementById("app")!);
CodePudding user response:
<For>
components keys items of the input array by the reference.
When you are updating a todo item inside todos with replace
, you are creating a brand new object. Solid then treats the new object as a completely unrelated item, and creates a fresh HTML element for it.
You can use createStore
instead, and update only the single property of your todo object, without changing the reference to it.
const [todos, setTodos] = createStore([
{ id: 1, text: 'cleanup' },
{ id: 2, text: 'groceries' },
])
const updateTodo = (id, text) => {
setTodos(o => o.id === id, "text", text)
}
Or use an alternative Control Flow component for mapping the input array, that takes an explicit key property: https://github.com/solidjs-community/solid-primitives/tree/main/packages/keyed#Key
<Key each={todos()} by="id">
...
</Key>
CodePudding user response:
While @thetarnav solutions work, I want to propose my own.
I would solve it by using <Index>
import { render } from "solid-js/web";
import { createSignal, Index } from "solid-js";
/*
* Returns a cloned array where the item at the provided index is replaced
*/
function replace<T>(array: Array<T>, index: number, newItem: T): Array<T> {
const clone = array.slice(0);
clone[index] = newItem;
return clone;
}
function App() {
const [todos, setTodos] = createSignal([
{ id: 1, text: "cleanup" },
{ id: 2, text: "groceries" }
]);
return (
<div>
<div>
<h2>Todos</h2>
<p>
Problem: whilst typing in one of the input fields, they lose focus
</p>
<Index each={todos()}>
{(todo, index) => {
console.log("render", index, todo());
return (
<div>
<input
value={todo().text}
onInput={(event) => {
setTodos((todos) => {
return replace(todos, index, {
...todo(),
text: event.target.value
});
});
}}
/>
</div>
);
}}
</Index>
Dat: {JSON.stringify(todos())}
</div>
</div>
);
}
render(() => <App />, document.getElementById("app")!);
As you can see, instead of the index
being a function/signal, now the object is. This allows the framework to replace the value of the textbox inline.
To remember how it works: For remembers your objects by reference. If your objects switch places then the same object can be reused. Index
remembers your values by index. If the value at a certain index is changed then that is reflected in the signal.
This solution is not more or less correct than the other one proposed, but I feel this is more in line and closer to the core of Solid.