Missing target element error & TypeError: Converting circular structure to JSON in controllers that don't create JSON

I’m sporadically seeing “Missing target element” JS errors fired during connect(), but they also come with an error log regarding circular JSON structure, in controllers that do not parse JSON, which seems very strange to me.

Some salient points for background:

  • I’m using Turbolinks, and I bundle Stimulus via Webpack.
  • The controllers exist once per page.
  • The targets that are mentioned in the error reports are not conditionally rendered, the entire component is either there or not.
  • The controllers that are erroring don’t handle JSON through any code paths that connect() invokes.
  • I’ve only seen this in Chrome on Windows.
  • It is very very infrequent, less than a percentage of my requests. I have not been able to repro it.
  • These controllers do some work in connect(). One accesses media devices and rebuilds some select elements. Another tests some data attributes and then does some class manipulation on its targets.

Here are a couple example logs of the issue:

10:34:57.960 0.000s   Log     document not ready yet, trying again in 500 milliseconds...
10:34:58.802 0.842s   Log     Error connecting controller {} TypeError: Converting circular structure to JSON
10:34:58.803 0.843s   Error   Missing target element "phone.bridged"
10:34:58.810 0.850s   Log     Error connecting controller {} TypeError: Converting circular structure to JSON
10:34:58.810 0.850s   Error   Missing target element "phone.dial"
10:35:05.016 7.056s   Error   #1192 Missing target element "preferences.listen" (occurrence 44159716805)
14:26:42.727 0.000s   Log     Error connecting controller {} TypeError: Converting circular structure to JSON
14:26:42.728 0.001s   Error   Missing target element "phone.bridged"
14:26:42.778 0.051s   Log     WebSocket opened successfully.
14:26:42.779 0.052s   Log     [PStream] Setting token and publishing listen
14:26:43.991 1.264s   Error   #1243 Missing target element "preferences.listen" (occurrence 44390982945)

My current thought is perhaps I shouldn’t do a lot of work in connect(). With Stimulus I’m no longer directly hooked into the Turbolinks load event. With previous JS component systems I’ve mounted components in response to turbolinks:load, and done teardown via turbolinks:before-cache. I’ve already added a teardown() method to my Stimulus base controller that I am now executing in response to turbolinks:before-cache to do DOM cleanup, perhaps I should add a similar lifecycle method for doing heavy lifting after the component is connected? This seems a little dramatic for a small quantity of errors though.

Are you copying these log entries from the browser’s console log or handling/generating them in your app? Possible you have a JSON.stringify() in there somewhere?

I’d try catching this error and logging the HTML to see if it’s really what you expect. Something like:

connect() {
  try {
    this.bridgedTarget
  } catch (error) {
    console.warn(error, `HTML: ${this.element.outerHTML}`)
  }
}