htmx icon indicating copy to clipboard operation
htmx copied to clipboard

TypeError: Converting circular structure to JSON error when migrating from 1.9.12 to 2.0.0

Open coin-au-carre opened this issue 1 year ago • 6 comments

Our code was working fine until we migrate from 1.9.12 to 2.0.0. When clicking the button to make a POST /dashboard/month we have now the following error

Console error

htmx.min.js:1 Uncaught 
TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'HTMLButtonElement'
    |     property 'htmx-internal-data' -> object with constructor 'Object'
    |     property 'listenerInfos' -> object with constructor 'Array'
    |     index 0 -> object with constructor 'Object'
    --- property 'on' closes the circle

Code

<button
  hx-post="/dashboard/month"
  hx-vals='js:{"date": getFirstDayOfmonthFromGivenDate(document.getElementById("month-picker-data").innerHTML, 0)}'
  hx-target="#monthPickerHeadDiv"
  hx-swap="innerHTML"
>
<div class="hidden" id="month-picker-data">
{ currentDate.Format("2006-01-02") }
</div>
<div
	id="monthPickerHeadDiv"
	hx-get="/dashboard/first/"
	hx-trigger="load"
	hx-target="this"
></div>
<script>
function getFirstDayOfmonthFromGivenDate(date, direction) {
	const givenDate = new Date(date);
	if (direction === 0) {
			newDate = new Date(givenDate.getFullYear(), givenDate.getMonth() - 1, 1);
	} else if (direction === 1) {
			newDate = new Date(givenDate.getFullYear(), givenDate.getMonth() + 1, 1);
	} else {
			throw new Error("Direction must be 0 or 1");
	}
	const year = newDate.getFullYear();
	const month = String(newDate.getMonth() + 1).padStart(2, '0'); // Months are 0-based
	const day = String(newDate.getDate()).padStart(2, '0');

   return `${year}-${month}-${day}`;
}
</script>

coin-au-carre avatar Jul 02 '24 22:07 coin-au-carre

Hi @coin-au-carre
I just had a quick test myself and on both 1.9.12 and 2.0.0 I was able to reproduce that same error when I tried to manually do JSON.stringify(elt) on a button element that has a active htmx listener. So i don't see what has changed in the new version to cause your issues as both do the same thing with internal event listener data. I don't think anything should be trying to stringify active event driven DOM elements like this though as this seems strange.

Try to avoid using the min.js version of htmx when diagnosing problems as it makes debugging and showing location of errors a lot harder. link to something like this for testing:

<script src="https://unpkg.com/[email protected]/dist/htmx.js"></script>

Then you should be able to get a better line number in your error to help track down what is doing the stringify wrong and you can enter the source tab in dev tools and add a breakpoint to debug if needed

MichaelWest22 avatar Jul 12 '24 03:07 MichaelWest22

Thanks @MichaelWest22 for your answer unfortunately I am not a seasoned JS dev and I am not sure I would provide meaningful insights. I would keep that in mind when we give a try again on v2 (I don't know when). As of now we stick to v1, hopefully this issue might be solved with another issue.

We can also avoid this issue by making our code better, I would be glad also if you think we are not doing things the right and propose another approach.

coin-au-carre avatar Jul 16 '24 08:07 coin-au-carre

Here the error with htmx.js without minification:

htmx.js:3916 Uncaught 
TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'HTMLButtonElement'
    |     property 'htmx-internal-data' -> object with constructor 'Object'
    |     property 'listenerInfos' -> object with constructor 'Array'
    |     index 0 -> object with constructor 'Object'
    --- property 'on' closes the circle
    at JSON.stringify (<anonymous>)
    at formDataFromObject (htmx.js:3916:37)
    at issueAjaxRequest (htmx.js:4214:28)
    at htmx.js:2538:13
    at HTMLBodyElement.eventListener (htmx.js:2444:13)
    at triggerEvent (htmx.js:2944:27)
    at handleTriggerHeader (htmx.js:1955:11)
    at handleAjaxResponse (htmx.js:4560:7)

coin-au-carre avatar Jul 18 '24 07:07 coin-au-carre

3916:  formData.append(key, JSON.stringify(obj[key])) <- here is where it is stringify'ing you button object which is breaking
4214:  const expressionVars = formDataFromObject(getExpressionVars(elt)) <- here is it calling the the function that is failing with the hx-vals looked up data

I copied the code snipits you supplied into my own project and tested it all myself and I was unable to reproduce and the form submitted with the date just fine for me.

It seems that hx-vals='js:{"date": getFirstDayOfmonthFromGivenDate(document.getElementById("month-picker-data").innerHTML, 0)}' in your button is some how returning a button element which makes no sense and doesn't do this in my testing. I would test by changing the format of this hx-vals to different javascript format and with different test js code and look at the examples in https://htmx.org/attributes/hx-vals/ for something to try. It should be javascript that returns a flat json object to be compatible with htmx 2 I think.

You could also try using js to pre fill out a hidden div or input with the value you want to submit and then using hx-include to link to the data stored inside the page which will prevent htmx having to do a JS eval to process it.

Also it is good to practice using the browsers developers tool and go to the htmx.js in sources tab and find line 3916 and 4214 and add breakpoints and run your website so you can see how it is failing and its a great way to learn JS.

MichaelWest22 avatar Jul 18 '24 13:07 MichaelWest22

Thanks for you answer that is quite weird because this getFirstDayOfmonthFromGivenDate(document.getElementById("month-picker-data").innerHTML, 0) gives a correct "2024-06-01" value string. If I directly use hx-vals='js:{"date": "2024-06-01"} it also gives me the error (with htmx v2.0.1 unminified)

htmx.js:3919 Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'HTMLButtonElement'
    |     property 'htmx-internal-data' -> object with constructor 'Object'
    |     property 'listenerInfos' -> object with constructor 'Array'
    |     index 0 -> object with constructor 'Object'
    --- property 'on' closes the circle
    at JSON.stringify (<anonymous>)
    at formDataFromObject (htmx.js:3919:37)
    at issueAjaxRequest (htmx.js:4217:28)
    at htmx.js:2541:13
    at HTMLBodyElement.eventListener (htmx.js:2447:13)
    at triggerEvent (htmx.js:2947:27)
    at handleTriggerHeader (htmx.js:1958:11)
    at handleAjaxResponse (htmx.js:4563:7)
    at xhr.onload (htmx.js:4345:9)
    

So I thought you were able to reproduce the error somehow:

I was able to reproduce that same error when I tried to manually do JSON.stringify(elt)


I have found parts of the code which I think are actually responsible of this error. I am using Golang and using events

 // At end of POST /dashboard/month handler
 // using https://github.com/angelofallars/htmx-go
// Commenting this displays no error (and also no change)
	_ = htmx.NewResponse().AddTrigger(htmx.TriggerObject("trigger-api-request-stats", map[string]string{
		"date": firstDayOfDateMonth.Format("2006-01-02"),
	})).Write(c.Response())
 // Removing trigger-api-request-stats  displays no error (and also no change)
 // Removing hx-vals="js:{...event.detail}" displays no error (and we can see the reload triggered without change)
		<div
			hx-post="/dashboard/api-request-stats/"
			hx-vals="js:{...event.detail}"
			hx-trigger="load, trigger-api-request-stats from:body"
			hx-target="this"
		></div>

I believe the hx-vals="js:{...event.detail}" is somehow the culprit. I will try to give a minimal reproductible example when I can.

coin-au-carre avatar Jul 30 '24 11:07 coin-au-carre

I believe the hx-vals="js:{...event.detail}" is somehow the culprit.

I would say it is indeed @coin-au-carre, as event.detail will contain all of the properties of the custom event, including the detail.elt that htmx sets to the element on/from which the event is fired, as can be seen here: https://github.com/bigskysoftware/htmx/blob/0f70de7c0f8da72b3667fa7aef44636e51caf94e/src/htmx.js#L2938-L2939

As Michael mentioned above,

I was able to reproduce that same error when I tried to manually do JSON.stringify(elt)

That's likely what you're getting here, as you pass the whole detail object, which contains an element reference

Hope this helps!

Telroshan avatar Jul 31 '24 20:07 Telroshan