Performance
Legend-State is already quite optimized by default, but there are some things to keep in mind to make sure it’s as optimized as possible.
Batching
Making multiple changes in a row can cause React components and observers to re-run multiple times when they should wait until changes are complete. So if you’re setting a lot of observables at once, it’s good to batch them together into one operation.
See Batching for more.
When persisting
If you are using synced
or syncObservable
to automatically persist your changes, you can prevent excessive writes by delaying persistence until changes are complete. Pushing to an array 1000 times could save to storage 1000 times, which could be very slow!
Iterating through observables creates Proxies
For most usage this effect is negligible, but may be a concern with huge arrays of objects.
Accessing objects/arrays in observables creates Proxies to give them the observable functions. If you are iterating through large objects that don’t need to be tracked for changes, call get()
first to access the raw data, skipping all the Proxy creation.
Arrays
Legend-State is especially optimized for arrays since it was built for Legend to handle huge lists of data. Here are a few tips to get the best performance out of arrays.
Arrays of objects require a unique id
To optimize rendering of arrays of objects, Legend-State requires a unique id
or key
field on each object. If your data needs to have a different id field, you can use a ${arrayName}_keyExtractor
function next to the array object:
Under the hood, Legend-State listens to elements by path within the object. Operations like splice
can change the index of an element which changes its path, so it uses the unique id
to handle elements being moved and keep observables as stable references to their underlying element. It also optimizes for repositioning items within arrays and only re-renders the changed elements.
Use the For
component
The For
component is optimized for rendering arrays of observable objects so that they are extracted into a separate tracking context and don’t re-render the parent.
You can use it in two ways, providing an item
component or a function as a child.
An optimized
prop adds additional optimizations, but in an unusual way by re-using React nodes. See Optimized rendering for more details.
For doesn’t re-render the parent
In this more complex example you can see that as elements are added to and update the array, the parent component does not re-render.
Don’t get() observables while mapping
The map
function automatically sets up a shallow listener, so it will only re-render when the array is changed and not when individual elements are changed. For best performance it’s best to let the child component track each item observable.
Make sure that you don’t access any observable properties while mapping, like accessing the id for the key, so use peek()
to prevent tracking. If you do get()
inside an observer
component would trigger the outer component to observe every list element.
Optimized rendering
The For
component has an optimized
prop which takes the optimizations even further. It prevents re-rendering the parent component when possible, so if the array length doesn’t change it updates React elements in place instead of the whole list rendering. This massively reduces the rendering time when swapping elements, sorting an array, or replacing some individual elements. But because it reuses React nodes rather than replacing them as usual, it may have unexpected behavior with some types of animations or if you are modifying the DOM externally.
This is how the fast “replace all rows” and “swap rows” speeds in the benchmark are achieved.