davidgilbertson/react-recollect

Spike: smarter updating of listeners

davidgilbertson opened this issue · 0 comments

Part 1 - parent path listeners

Currently, if there are two listeners:

  • <ParentComponent> listening to store.tasks
  • <ChildComponent> listening to store.tasks.1.done

Then if the prop at path store.tasks.1.done is updated, then both components are updated. I did this for a specific reason in the early days and really should have documented why.

Edit, this is because a child component will often not get its data from the store passed by collect() - it will get the data passed from the parent (e.g. a <TaskList> will pass each task object from an array to a child <Task> component). So if I don't update the <TaskList> component with the new store, it wouldn't know if a task was ticked.

At the very least I need to add a test to protect/document this.

Part 1 result: no change to code, but a change to guidance:

"You don't need to wrap a component in collect unless you want access to the store in that component". Two reasons for this:

  • If <TaskList> references store.tasks and renders a bunch of <Task> components, and the <Task> component is not wrapped in collect, then all of the reads in the <Task> components (store.tasks.0.name, etc) are attributed to the <TaskList> - so it's listening on everything its children rendered.
  • And a second reason I've just forgotten.

Also, if you aren't wrapping your components in collect() make sure you're using PureComponent or React.memo. To not use these is to be slow for no reason.

Part 2 - child path listeners

I also update down the prop tree. E.g. if a prop store.tasks changes (an entire array is overwritten with a new array), then I'll trigger updates on components listening to paths that start with that. So, store.tasks.0, store.tasks.0.done and so on.

In other words, if you're listening to any prop 'inside' a prop that changes, you need to know about that parent prop changing. But, if a listener is listening to a child prop and is a child component then it won't need to update, because the parent component will update. But I have no way of knowing the relationships between components.

I don't think there's much I can do here. And, I think this falls under the control of React, which will not be wasteful even though I've requested redundant updates.

Part 2 Result: no change

Part 3 - tracking previous value

What if, for each listener, I stored the last value at the prop path a component is listening to? Then, when updating, if the value hasn't changed, don't bother updating. Would this catch anything?

  • For updating listeners with an exact match on the path, it makes no difference (the proxy handler won't trigger an update if the value didn't change).
  • For listeners to a parent prop path, it won't matter, because if a value changed for any prop path, then all parent objects in the store will be different (because immutability).
  • But for listeners to a child path (I overwrite store.data and some component is listening to store.data.page.title), then if the title didn't change, I don't need to update it. But a component listening to store.data.page.title must also be listening on store.data.page and that will be a new object.

Also, would this take a lot of memory?

Part 3 Result: no change

Part 4 - listening on objects and arrays

Here's a question: do I need to listen to props that are objects/arrays? (And I mean {} objects, not null, etc). You can't actually use an array or an object without accessing one of its child properties.
I mean, you can't render a task to the screen. You can only render task.done and task.name and task.id and so on. Even if you called JSON.stringify(task) that calls everything under the hood.

Hmmm, I think I do need to listen to objects/array, because of part 1 above. I might only read an object in a parent component (store.page), then pass that object to a child component (regardless of whether it's wrapped in collect or not) and it reads a property that's a string page.title. So yeah, I need to be listening for changes to the object, because Recollect might not be aware of child components listening to child props.

Part 4 result: no change


Well this is good and bad. I think it's about it's efficient as it can be regarding updating, unless I can think of a way to not trigger an update on a child component if I'm updating a component further up the tree (and only bother if React is handling this).