Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.JS compatibliity #2959

Closed
chungwong opened this issue Feb 24, 2025 · 7 comments · Fixed by #2968 or #2969
Closed

Next.JS compatibliity #2959

chungwong opened this issue Feb 24, 2025 · 7 comments · Fixed by #2968 or #2969

Comments

@chungwong
Copy link

This line cause trouble when GridStack is used in Next.js

'use client';
import 'gridstack/dist/gridstack.min.css';
import { GridStack } from 'gridstack';
import { useEffect } from 'react';

export default function App() {
  useEffect(() => {
    let grid = GridStack.init();
  });

  return null;
}

ReferenceError: Cannot access 'Utils' before initialization
@adumesny
Copy link
Member

@Aysnine ^^^

@Aysnine
Copy link
Contributor

Aysnine commented Feb 27, 2025

This is a tricky one, and let's start with the conclusion: gridstack works fine in nextjs 14, but in nextjs 15 it has the problem you describe.

I need to look into this, until then you can try nextjs 14: npx create-next-app@14

@Aysnine
Copy link
Contributor

Aysnine commented Feb 27, 2025

@adumesny

Two module have a cyclic relationship: class Utils and class GridStack. That should be the key clue.

My guess is that there may have been a change in nextjs15's packaging strategy that caused the handling of circular dependencies to behave differently than before. I'll explore nextjs15's changes in this regard.

Maybe also improve the gridstack cyclic dependency code?

@adumesny
Copy link
Member

adumesny commented Feb 27, 2025

this would be the place... an API breakage if it were to move GridStack.renderCB elsewhere

  /** create the default grid item divs, and content possibly lazy loaded calling GridStack.renderCB */
  static createWidgetDivs(itemClass: string, n: GridStackNode): HTMLElement {
    const el = Utils.createDiv(['grid-stack-item', itemClass]);
    const cont = Utils.createDiv(['grid-stack-item-content'], el);

    if (Utils.lazyLoad(n)) {
      if (!n.visibleObservable) {
        n.visibleObservable = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) {
          n.visibleObservable?.disconnect();
          delete n.visibleObservable;
          GridStack.renderCB(cont, n);
          n.grid?.prepareDragDrop(n.el);
        }});
        window.setTimeout(() => n.visibleObservable?.observe(el)); // wait until callee sets position attributes
      }
    } else GridStack.renderCB(cont, n);

    return el;
  }

or move it out of Util (likely better smaller break)

@Aysnine
Copy link
Contributor

Aysnine commented Feb 27, 2025

@adumesny

add cb param?

  /** create the default grid item divs, and content possibly lazy loaded calling GridStack.renderCB */
  static createWidgetDivs(itemClass: string, n: GridStackNode, cb: (cont: HTMLElement, n: GridStackNode) => void): HTMLElement {
    const el = Utils.createDiv(['grid-stack-item', itemClass]);
    const cont = Utils.createDiv(['grid-stack-item-content'], el);

    if (Utils.lazyLoad(n)) {
      if (!n.visibleObservable) {
        n.visibleObservable = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) {
          n.visibleObservable?.disconnect();
          delete n.visibleObservable;
          cb(cont, n);
          n.grid?.prepareDragDrop(n.el);
        }});
        window.setTimeout(() => n.visibleObservable?.observe(el)); // wait until callee sets position attributes
      }
    } else cb(cont, n);

    return el;
  }
  public addWidget(w: GridStackWidget): GridItemHTMLElement {
    if (typeof w === 'string') { console.error('V11: GridStack.addWidget() does not support string anymore. see #2736'); return; }
    if ((w as HTMLElement).ELEMENT_NODE) { console.error('V11: GridStack.addWidget() does not support HTMLElement anymore. use makeWidget()'); return this.makeWidget(w as HTMLElement); }

    let el: GridItemHTMLElement;
    let node: GridStackNode = w;
    node.grid = this;
    if (node?.el) {
      el = node.el; // re-use element stored in the node
    } else if (GridStack.addRemoveCB) {
      el = GridStack.addRemoveCB(this.el, w, true, false);
    } else {
      el = Utils.createWidgetDivs(this.opts.itemClass, node, (cont, n) => {
        GridStack.renderCB(cont, n);
      });
    }

    if (!el) return;

    // if the caller ended up initializing the widget in addRemoveCB, or we stared with one already, skip the rest
    node = el.gridstackNode;
    if (node && el.parentElement === this.el && this.engine.nodes.find(n => n._id === node._id)) return el;

    // Tempting to initialize the passed in opt with default and valid values, but this break knockout demos
    // as the actual value are filled in when _prepareElement() calls el.getAttribute('gs-xyz') before adding the node.
    // So make sure we load any DOM attributes that are not specified in passed in options (which override)
    const domAttr = this._readAttr(el);
    Utils.defaults(w, domAttr);
    this.engine.prepareNode(w);
    // this._writeAttr(el, w); why write possibly incorrect values back when makeWidget() will ?

    this.el.appendChild(el);

    this.makeWidget(el, w);

    return el;
  }

@adumesny
Copy link
Member

yeah, I just moved it over. I don't think many people use that newer Util code as that was added recently for delay loading, and lib does it automatically, and wrapper like Angular have their own delayLoading code anyway...

should be fixed in next release. either way CB would have been breaking too..

adumesny added a commit to adumesny/gridstack.js that referenced this issue Feb 27, 2025
@Aysnine
Copy link
Contributor

Aysnine commented Feb 28, 2025

@chungwong @adumesny

Checked. Nextjs15 works fine with [email protected]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants