#setStore call is causing weird "re-render" glitch inside Firestore real-time callback function.

28 messages · Page 1 of 1 (latest)

static minnow
#

To simplify the example I'll use todos.

const [todos, setTodos] = createStore([])

let unsubscribe;

onMount(() => {
  unsubscribe = onSnapshot(collection("pathToTodos"), snapshot => {
  if(!todos.length) {
  setTodos(todosFromSnapshot)
} else {
 const todoToUpdate = todos.filter((todo, i) => todo.propToFilterOn !== todosFromSnapshot[i].propToFilterOn)[0]
 setTodos((todo) => todo.id === todoToUpdate.id, todoToUpdate)
}
})
})

onCleanup(() => unsubscribe && unsubscribe())

return (
 <Show when={todos.length}>
  <For each={todos}>
    {(todo, index) => {
        <Todo todo={todo} index={index()} />
    }}
  </For>
</Show>
)

I'm trying to listen to changes in Firestore and update the Todo that has changed completely, not just one property but for the sake of this example when the button is clicked it just updates the Todo in Firestore which then triggers the onSnapshot callback, the store is not updated in the onClick handler. It works fine until I use two devices and try to update them close together. It appears like the list of todos "re-renders" at the same time as the second "click" and so the "click" isn't registered but instead the focus/hover state appears and the button must be clicked again to fire the function.

This was happening to me in React even when using Memo, useCallback. I set the background colour to change on every re-render and the component definitely wasn't re-rendering but I still had this glitch happening when the snapshot is triggered. I thought that because in React you use .map to create a new array that this was causing the issue.

I thought with Solid's createStore that every object in the array is its own signal, so updating one object in the store would not affect other objects in the array.

Not sure how to add video or gifs here but added a screen record to better show it https://i.makeagif.com/media/6-01-2023/-nTwSu.gif

Any advice? Only one todo is ever updated at one time and I only want that specific one to re-render.

vital arch
#

i think it could be solved w reconcile

#

(ps you can use triple backticks to format code: like this or with a single backtick it looks like this)

vital arch
# vital arch i think it could be solved w reconcile

I assume the issue is that ur getting a complete new object from onSnapshot, so u wipe out all the signals and recreate them with each update. With reconcile there is a diff that happens between the two objects, and only the properties/signals that have changed will be updated (and not re-created)

static minnow
# vital arch I assume the issue is that ur getting a complete new object from onSnapshot, so ...

Thank you, I'll look into that. I'm not wiping out all the signals though? It's just initially the store is an empty array so on mount I'm setting it to the snapshot value, every further update is done using setStore which should only update the individual object? Unlike React where you have to map over and return a new array. At least that was my understanding. I'll look into reconcile though.

vital arch
#

very likely i am misunderstanding the issue 🙂

#

It appears like the list of todos "re-renders" at the same time as the second "click" and so the "click" isn't registered this makes me believe the component is being mounted/unmounted

#

which would indicate that the signal has been recreated

static minnow
#

Me too, just picked up Solid for this specific case as React had the same problem. It's definitely not the receiving of the snapshot that is causing the glitch, if I remove the updated Todo code then there is no problem with the click registering.

I don't think it could be unmounting because the listener would be removed with the unsubscribe() call.

vital arch
#

could u log on onMount in the Todo component to see if the component is being re-created? and in the else { ... } in onSnapshot to make sure it's doing the granular update?

#

if I remove the updated Todo code then there is no problem with the click registering.
wdym with the updated Todo code?

static minnow
#

Will try that in a second.

I mean if I remove the else block completely and don't do the granular update so just set the todos onMount then the button clicks work correctly, that was just to discount the onSnapshot firing from causing the issue because it still fires on the updates but doesn't call setTodos and so doesn't update the UI, it's definitely coming from the setTodos call in the else block.

vital arch
#

mmm yes, that does sound like it should do the proper granular updates then

#

if u wanna codesandbox it or something I can have a look at it

static minnow
#

console logging in the onMount of the button works as expected, but it only logs when the list renders initially and then the individual one logs when it is remounted. In the gif when the lock icon is removed the "Book Now" button comes back which is where it is remounted. Not sure why this glitch is happening. Will read up on the reconcile function though.

#

won't be able to put this out publicly but will try and put up a demo over the weekend.

#

thanks for your help

vital arch
#

ooooo yes

#

i see

#

setTodos((todo) => todo.id === todoToUpdate.id, todoToUpdate) should become setTodos((todo) => todo.id === todoToUpdate.id, reconcile(todoToUpdate))

#

i think..

#

another option would be to use <Index/> instead of <For/>

#

For does referential checks on the signal, Index only cares about the index

#

u could also yolo it and just always do setTodos(reconcile(todosFromSnapshot)) but will be a bit less performant

static minnow
#

Thank you will try that and see.

static minnow
# vital arch the components should only mount on the first render

yeah but they are unmounted when the locked button component is mounted instead and then mounted again when the lock button is unmounted.

The reconcile function didn't fix it, still the same issue. Probably best to build a demo that people can see, will work on it.