justin-schroeder / arrow-js

Reactivity without the framework
https://arrow-js.com
MIT License
2.32k stars 50 forks source link

Script throwing "Illegal Invocation" when adding an object to a reactive array #75

Closed jopicornell closed 4 months ago

jopicornell commented 1 year ago

Hi there!

I'm migrating a personal project from Vue to arrow-js, as I love the idea behind this project. I'm using webmidi and when trying to set an array of MIDIInputs to a reactive property, it throws an "Illegal Invocation" right away. The cause of that is that at some point, the reactivity makes the function "onmidimessage" of a MIDIInput to be called without the right context:

import {Message, WebMidi} from 'webmidi';
import {reactive, watch} from "@arrow-js/core";

const midi = reactive({ inputs: [] })

WebMidi.enable()
  .then(() => {
    midi.inputs = WebMidi.inputs
  })

This doesn't happen when using Vue and its ref() function. I think the problem here is that I don't want to make a deep reactive array, but a shallow reactive array, and only when the array is modified, it should call reactive dependencies and update the template.

This happens with native browser objects, like MIDIInput or any DOM object, because there are some functions that should not be modified or called, or because the context is lost.

A reproducible script using DOM, to make the life easy to anyone wanting to reproduce this would be:

<body>
  <button>Hello</button>
  <script>
    const data = reactive({
      buttons: []
    })
    data.buttons = document.querySelectorAll("button")
  </script>
</body>

You can find here a reproducible stackblitz. I'd be happy to collaborate with the project in any way!

justin-schroeder commented 1 year ago

Thanks for the repro! I’ll definitely look into this

HugoDF commented 10 months ago

@justin-schroeder The issue is occuring for HTMLElements and NodeList (and more I'm sure).

Probably the expectation for the following is that it doesn't crash:

const data = reactive({
  button: document.querySelector("button"),
  buttons: document.querySelectorAll('button')
})

I've looked into how it works in Vue.js, it's skipping reactivity any "object" that doesn't report being one of "'Object', 'Array', 'Map', 'Set', 'WeakMap', 'WeakSet'".

From a types point of view the following shouldn't really work, buttons is being initialised to an Array and then being set to a NodeList, but I guess it could be fixed also?

const data = reactive({
  button: {},
  buttons: [],
})
data.button = document.querySelector('button')
data.buttons = document.querySelectorAll("button")

I've got a fix for these in https://github.com/justin-schroeder/arrow-js/pull/87