S
SolidJS9mo ago
Zwel

Solid doesn't re-render when update the same array in createSignal

Expected behavior: Re-render inputs every time passwords array is updated even with the same value, to avoid duplicating value in the same input.
import { Component, For, createSignal } from 'solid-js'

const App: Component = () => {
const [passwords, setPasswords] = createSignal(['', '', ''])

const setPassword = (v: string, index: number) => {
const value = v.split('').reverse()[0]

setPasswords(old =>
old.map((oldValue, currentIdx) => {
if (currentIdx === index) return value

return oldValue
})
)
}

return (
<For each={passwords()}>
{(v, idx) => {
console.log('render the inputs')

return (
<input
type="number"
min="0"
max="9"
step="1"
maxLength="1"
value={v}
onInput={e => setPassword(e.target.value, idx())}
/>
)
}}
</For>
)
}

export default App
import { Component, For, createSignal } from 'solid-js'

const App: Component = () => {
const [passwords, setPasswords] = createSignal(['', '', ''])

const setPassword = (v: string, index: number) => {
const value = v.split('').reverse()[0]

setPasswords(old =>
old.map((oldValue, currentIdx) => {
if (currentIdx === index) return value

return oldValue
})
)
}

return (
<For each={passwords()}>
{(v, idx) => {
console.log('render the inputs')

return (
<input
type="number"
min="0"
max="9"
step="1"
maxLength="1"
value={v}
onInput={e => setPassword(e.target.value, idx())}
/>
)
}}
</For>
)
}

export default App
5 Replies
foolswisdom
foolswisdom9mo ago
This is the default behavior. If you want to change it, pass { equals: false } as the second option of createSignal. See https://docs.solidjs.com/reference/basic-reactivity/create-signal
Zwel
ZwelOP9mo ago
I tried {equal: false}, but result is the same. Not re-render every time.
thetarnav
thetarnav9mo ago
wrap each string in an object so that For can compare unique references right now it compares strings, because those are the items, where they are compared by value, which can have undefined behaviors like this or use Index if you don’t plan on reordering the passwords
Maciek50322
Maciek503229mo ago
so it doesn't rerender because of this:
const value = v.split('').reverse()[0]
const value = v.split('').reverse()[0]
which btw can be rewritten as
const value = v.at(-1) ?? ""
const value = v.at(-1) ?? ""
then you set this value to your signal. Renders for lists in For / Index are triggered when reference / value changes, so something like from your example with
passwords().map((v, idx) => <>...<>) // ❌ avoid this
passwords().map((v, idx) => <>...<>) // ❌ avoid this
should work, but would be very inefficient, as every element would be recreated each time 1 value changes. My suggestion is to: - set input value by reference to the input after you change it - consider using stores for nested objects, it just have nicer syntax and reacts to only signle element that changed - use rather Index than For when the enumerating value is primitive (not an object) So finally something like this
const [passwords, setPasswords] = createStore(["", "", ""]);
let inputs: HTMLInputElement[] = [];
const setPassword = (v: string, index: number) => {
const value = v.at(-1) ?? "";
setPasswords(index, value); //clean syntax isn't it? :)
inputs[index].value = value; // update input directly yourself
//...
};
return <Index each={passwords}> //Index instead of For
{(v, idx) => {
console.log("render the inputs");

return (
<>
<input
ref={inputs[idx]} // new
...
value{v()} // v() instead of v
onInput={(e) => setPassword(e.target.value, idx)} // idx instead of idx()
/>
</>
);
}}
</Index>
const [passwords, setPasswords] = createStore(["", "", ""]);
let inputs: HTMLInputElement[] = [];
const setPassword = (v: string, index: number) => {
const value = v.at(-1) ?? "";
setPasswords(index, value); //clean syntax isn't it? :)
inputs[index].value = value; // update input directly yourself
//...
};
return <Index each={passwords}> //Index instead of For
{(v, idx) => {
console.log("render the inputs");

return (
<>
<input
ref={inputs[idx]} // new
...
value{v()} // v() instead of v
onInput={(e) => setPassword(e.target.value, idx)} // idx instead of idx()
/>
</>
);
}}
</Index>
Also there's other way, without ref and updating input value yourself. It's to pass a signal that does trigger when you set it, that is: example with signal & For
<input
value={passwords()[idx()]}
...
/>
<input
value={passwords()[idx()]}
...
/>
for store & Index I have no better idea yet than that:
const setPassword = (v: string, index: number) => {
const value = v.at(-1) ?? "";
batch(() => {
setPasswords(index, "");
setPasswords(index, value);
});
};
...
<input
value={passwords[idx]}
...
/>
const setPassword = (v: string, index: number) => {
const value = v.at(-1) ?? "";
batch(() => {
setPasswords(index, "");
setPasswords(index, value);
});
};
...
<input
value={passwords[idx]}
...
/>
because store also trigger change only when value is different, that's why I set passwords value to "" then to desired value. Wrapped in batch, so that first set to "" doesn't trigger anything - without batch input value would change to "" then to desired value but very quickly, this way it changes directly to desired value and is notified that it changed, even though it didn't
Zwel
ZwelOP9mo ago
@Maciek50322 Thank you for your clarifying. It helps me a lot.
Want results from more Discord servers?
Add your server