cerebral / overmind

Overmind - Frictionless state management

Home Page:https://overmindjs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Lessons learned (useful for documentation?)

grandevel opened this issue · comments

Per @christianalfoni's request in discord... documenting some quick lessons learned from when I inherited an Overmind/React app. Initial Path Count > 62k and 9k connected components. App performance was mediocre. With 1 hour's work, it was possible to take the path count and connected components down to ~3k (95% saving on state access and proxy tracking overheads) and 1k connected components. Mem usage down, and app speeds significantly improved.

Main wins worth me documenting...

  • Lists as Objects, not arrays. Covered here already - https://overmindjs.org/guides-1/managing-lists - but many are not understanding why this is important. If you use .find or .filter, then Overmind will detect the proxy access - and create a path that needs to be tracked... for every subobject. What then becomes worse is any object update will cause a significant re-render - including in the objects that have no changes, because you are accessing all objects in a list with .find, for example. It will likely cause a parent to re-render un-necessarily.

The key is to absolutely avoid using .find(x => x.id === someId) in sub components (like a TodoItem or a Todo list). The worst example is to {todoList.map(item => { return { }}) - and then in the to pull the list from state and todoList.find(item => item.id === id) to get the object you are going to bind to the render.

A lighter approach typically seems to work better when passing props - pass id's, rather than objects themselves, and grab them from state directly using the object-as-list: state.todoList[id].

  • Create the list object in the effect. Using derived isn't the best solution (per the article), because it gives other implementors a 'choice' on how to pull items from a list in state. Instead reduce your list to an object with a unique key in the effect (or even better, update the server response if you have control!) and put that in state. Here is an example:

const listToObject = (list, prop) => {
return list.reduce(
(a: Object, c: Object) => {
const id = c[prop];

  if (a[id]) return null; // We could not do this - the key provided is not duped - throw some sort of exception here
  a[id] = c;
  return a;
},

Two options exist to add a list of all keys - use Object.keys, or create a .all property with a list of ID's.

  • Replacing objects and lists - including objects replacing lists like the point above - consider if you should actually replace the whole object-as -a-list object, or just swap out the relevant properties when an update occurs.

Example: fetching todos from the server every 5 seconds returns a list of 20 todo objects. 1 of them changes, bad thing you can do from a path tracking perspective is update the whole list object and cause a widespread re-render. So, instead, consider whether it is better to just take the hit, or better compare the sub-properties (i.e. each item) between objects and just update those sub properties which have changed without updating the root object containing the list.

  • Last one, and this is probably obvious, but discourage component rendering self-responsbility - allowing an object to decide if it should render itself creates un-necessary components, and also you might be creating references to objects in state (and therefore un-necessary proxy tracking) if you decide to nullify the render after all the work is done. Instead, try and use the parent object to determine whether the component should be created at all.

Use Overmind devtools to check your path, flush and connected component counts - biggest win for this app in my case.

Closing my own issue here, probably will do a small video series on this stuff

@grandevel interesting, thanks for sharing.
I remember I had similar problems with mobx.
Update this issue when you have made the videos, thanks!
🙏