3

I have a writable store

export default writable({})

My server is constantly pushing updates to this store and those handlers are in a js file

const handlers = {
    newUser: async (user) => {
        userStore.update(user => {
            userStore[user.id] = user;
            return userStore;
        });
    },
    statusChange: ...
    more...
};

A user object has multiple nested objects and arrays. My users page has a #each loop, and for every user, it passes the user to the <User /> component. The user component then passes other nested objects to other components, etc. The issue I'm having is that when I change a deeply nested value, the hundreds of user objects and the components they're used in re-run their reactive statements. Since the values mostly haven't changed, most of them won't rerender, but I have heavy code that needs to run on change, and this code is getting re-ran even if the component instance relates to a user that hasn't changed. I tried having nested stores, but then my handlers need to get(userStore) every single update, and so on for nested stores until I reach the one that actually was meant to be updated. Another issue with this is: it didn't solve the problem of updates down the chain when the change happens at the top level, ie adding a new key: {user} to the userStore.

So is there a proper way to do this kind of state management, without causing updates where there are no changes?

Thank you.

H.B.
  • 166,899
  • 29
  • 327
  • 400
LAZ
  • 402
  • 4
  • 15
  • 1
    Maybe flatten the hierarchy a bit? – H.B. Jul 20 '22 at 18:45
  • 1
    @H.B. Not sure if that's possible, I did that as much as I could, but say the attributes of a user, they almost certainly have to be children of the userStore. And if the user has items (an array), and those items all have attributes, these should be attrs of the items. In any case, adding a key to the userStore will still cause updates to every other user in the store and there's no way to pre-define these users. It's all highly volatile data. – LAZ Jul 20 '22 at 18:50
  • You should consider breaking up your big single store into a set of smaller ones: a `users` store that would only hold, say, an ordered list of user IDs (so basically an array) and single `user` stores created and managed on the fly. This way CRUD user operations will only impact your `users` array (on create/delete) and a single `user` store. Surely your single `User` components don't need to know when other users' data has been changed. – Thomas Hennes Jul 21 '22 at 07:50
  • @ThomasHennes Yes this an idea I've thought of, but it raises some questions. So say either one changes, and my users page tries to rerender to show those changes, how do I guarantee the object is actually there when it goes to read it? Plus having multiple objects to keep track of manually that have the same top level keys, except one is a store object (key: val) and the other is just an object with a store per key. – LAZ Jul 21 '22 at 13:15
  • Obviously I don't know the implentation details of your data updates (websocket or polling? monolithic [one big update hitting many different objects] or small individual updates?), but imo it's those data updates that should trigger/drive re-renders, not the other way around. This way you're guaranteed that data is 'there' and is not stale. I would also focus on handling smaller/simpler components (away from anything monolithic). But again, hard to to give you more accurate advice w/o a more detailed understanding of your implementation details. – Thomas Hennes Jul 21 '22 at 16:04
  • @ThomasHennes Yeah of course. I treat the larger stores as sources of truth, their data is always updated from one file: the websocket handler funcs. Some updates replace the entire structure (ie when a user searches for data and the filters changed) and others are updates to the current data (when the filters haven't changed). So when a user has the page open, whatever they have searched will constantly be live updating: any time a change occurs on the backend that relates to the data visible to that user. My components are quite simple and split up into sub-components. – LAZ Jul 21 '22 at 16:40
  • @ThomasHennes Here's an example of a structure and the components involved: orders component (contains each loop, orders object comes from the orderStore) order component (gets passed the order) orderItem component (each item in the order gets passed to these) there's some heavy math happening per order to calculate different rates and returns depending on what user permissions you have, and conditional sorting applied to some parts, items, transactions, user (ids), and it changes when clicking column headers etc.. – LAZ Jul 21 '22 at 16:47

1 Answers1

1

The one thing I can think of is using <svelte:options immutable /> and derived stores.

By making components immutable, you get more optimized and limited updates and derived stores introduce their own change tracking, so if you pull out a property of an object, any other changes to the object will not trigger that store's subscribers.

E.g. assume the user store has a name property, you can decouple it a component like this:

<svelte:options immutable />
<script>
    import { derived } from 'svelte/store';
    
    export let user;
    
    const name = derived(user, $user => $user.name);
    $: $name, console.log('Name changed.');
</script>

<div>
    Name: {$name}
</div>

Likewise, instead of applying {#each} directly to any nested property, you can pull it out and separate it from the other changes.

More complex REPL example (Try removing immutable and/or the derived stores.)

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • Can I derive the user object? so inside User.svelte `export let user;` `const derivedUser = derived(user, $user => $user);` Or does that just work with primitives, or does the immutable make it work because now it's a simple reference check. I'll play a bit with derived stores, this might be the way. – LAZ Jul 20 '22 at 19:32
  • Returning the same object probably does nothing. The store change logic uses [`safe_not_equal`](https://github.com/sveltejs/svelte/blob/4617c0d5f5af1dae09cc00ce0134e433588a62d1/src/runtime/internal/utils.ts#L39) to determine whether subscribers are notified. The function does not consider `object` values the same, so this will still cause quite a few notifications for any non-primitive values, but the immutable components should prevent redundant updates because of the reference check. – H.B. Jul 20 '22 at 19:59