bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
37.23k stars 1.26k forks source link

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

Open coin-au-carre opened 2 months ago

coin-au-carre commented 2 months ago

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>
MichaelWest22 commented 2 months ago

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/htmx.org@2.0.0/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

coin-au-carre commented 1 month ago

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 commented 1 month ago

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)
MichaelWest22 commented 1 month ago
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.

coin-au-carre commented 1 month ago

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.

Telroshan commented 1 month ago

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!