question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Modifying an observable array does not update the view via the each directive

See original GitHub issue

Using push to add items to an observable array does not update the view using the each directive. Here is an example where clicking the “Add item” button does not add any new li to the rendered ul.

import {r} from 'mobx-jsx'
import {observable} from 'mobx'

function App() {
  const items = observable([
    {name: 'Item 0'},
  ])

  const onAddItemClick = () => {
    items.push({name: 'Item ' + items.length})
  }

  return <MyList items={items} onAddClick={onAddItemClick} />
}

function MyList({items, onAddClick}) {
  return <>
    <ul>
      <$ each={items}>
        { item => <li>{(item.name + ` (items: ${items.length})`)}</li> }
      </$>
    </ul>
    <button onClick={onAddClick}>Add item</button>
  </>
}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
ryansolidcommented, Jul 21, 2019

Alright, so big changes to the runtime allows each framework to adopt their own syntax. I now have arrays working the way you expect (as well as support for class components). Instead of the old control flow, you use the map function that takes an observableArray as it’s first argument and map iterator as the 2nd. It properly handles of MobX array mutators. I use the technique you highlighted in your earlier post so thanks for that. I still use my optimized memoized mapper as well so it’s win-win.

Released in v0.6.0

1reaction
kiejocommented, May 5, 2019

Thanks for looking into this in such detail.

However, the each control flow does not track key iterations for arrays since the performance would plummet (it would create N subscriptions instead of 1). It’s much more expensive in large lists to manage the reactive graph than it is to just clone the Array.

I definitely don’t like the idea of creating N subscriptions, but I think it should be possible to get away with just a single subscription on the array level with a library like MobX. I’m pretty sure this is already what happens when you use methods like slice, map, indexOf, etc on an observable array in MobX. This is the code that handles these cases in MobX: https://github.com/mobxjs/mobx/blob/333dfe1719712f8e0729bca8b7f05960b2838f5c/src/types/observablearray.ts#L473

Adjusting my example code to access one of these methods (in this example slice) does indeed solve the issue and the view is automatically updated when push is used on the array:

function MyList({items, onItemClick, onAddClick}) {
  function getItems() {
    items.slice() // Access slice to mark the array as observed
    return items
  }

  return <>
    <ul>
      <$ each={getItems()}>
        { item => <li model={item} onClick={onItemClick}>{(item.name)}</li> }
      </$>
    </ul>
    <button onClick={onAddClick}>Add item</button>
  </>
}

Observing arrays like this works out of the box in the context of React as there are no directives and the array methods can directly be used in the render method of a component. In React you would for example use {items.map(item => <li>{(item.name)}</li>)} instead of the directive, which will automatically mark the array as observed and rerender the component on array changes.

Wouldn’t it be possible for this library to make use of the observe feature of MobX when the each directive is used? This should also make it possible to clean up in case the directive gets “unmounted” as observe returns a dispose function. On a side note, wouldn’t it theoretically be possible to use the change data provided to the observe callback to skip most of the work done in reconcileArrays? I have no idea how much of a performance difference this would make, but it could be something interesting to explore.

So far I only have a rough understanding of how the different libraries (mobx-jsx, dom-expressions, babel-plugin-jsx-dom-expressions) work together, but I assume that such logic would ideally be implemented in mobx-jsx as it looks to be specific to how MobX works.

It is pretty common these days to handle arrays as frozen, both VDOM libraries like React do that, and even reactive libraries like Svelte.

I agree that immutability seems to be the direction more and more libraries and frameworks are taking, but it is very opinionated and unfortunately makes it impossible to integrate with third-party libraries that need to mutate arrays or objects in place. I also like the idea of simply being able to use standard array methods and everything just working out of the box with no special rules. Obviously there are many trade-offs involved, but if a solution without a negative impact on performance is possible, it might be worth it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

knockout.js - Change in observable array in ... - Stack Overflow
The problem here seems to be that code: me.course()[0].subjectList().push(me.subjectObject()[0]);. Calling .subjectList().push(.
Read more >
Docs • Svelte
Because Svelte's reactivity is based on assignments, using array methods like .push() and .splice() won't automatically trigger updates.
Read more >
3 Common Rxjs Pitfalls (and how to avoid them)
These are 3 situations that we can come across while building Angular apps using RxJs. We are going to go over why the...
Read more >
AsyncPipe - Angular
The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted,...
Read more >
Reactivity in Depth - Vue.js
When you modify them, the view updates. ... JavaScript doesn't usually work like this. ... When we mutate A0 , A2 does not...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found