Skip to content

Next.JS compatibliity #2959

Description

@chungwong

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

Activity

  1. adumesny commented on Feb 24, 2025

    @adumesny
    Member
  2. Aysnine commented on Feb 27, 2025

    @Aysnine
    Contributor

    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

  3. Aysnine commented on Feb 27, 2025

    @Aysnine
    Contributor

    @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?

  4. adumesny commented on Feb 27, 2025

    @adumesny
    Member

    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)

  5. Aysnine commented on Feb 27, 2025

    @Aysnine
    Contributor

    @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;
      }
  6. added a commit that references this issue on Feb 27, 2025
    80a1182
  7. adumesny commented on Feb 27, 2025

    @adumesny
    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..

  8. added a commit that references this issue on Feb 27, 2025
    271efcc
  9. Aysnine commented on Feb 28, 2025

    @Aysnine
    Contributor

    @chungwong @adumesny

    Checked. Nextjs15 works fine with gridstack@11.4.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions