javascriptruby-on-railsrubystimulusjsuppy

How to use disconnect() to close a Uppy JS instance – Stimulus


When I click the "back button", my Uppy form is briefly loaded twice. How can I get Stimulus to close the previous Uppy instance when I leave the page?

Following the Uppy Docs, I'm hoping something like this (code below) but I get the error: uppy is nil or undefined on disconnect().

How can I bind uppy.close() to the original instance of Uppy?

import { Controller } from "@hotwired/stimulus"
import Uppy from "@uppy/core"

export default class extends Controller {

  connect() {
    const uppy = new Uppy()
  }

  disconnect() {
    // This causes an error
    uppy.close();
  }

}

Solution

  • The problem is that you are trying to access uppy as a local variable that you have set in the connect method. However, methods to get get access to other methods' scope, which is why uppy is undefined when you try to access it in the disconnect method.

    Instead, you have a few options.

    Option 1 - Store the variable on the class instance

    Each class instance has its own this variable, and you an easily store values on it as it is an object.

    By convention, the below example uses this._uppy (with an underscore) but any name will work.

    import { Controller } from "@hotwired/stimulus";
    import Uppy from "@uppy/core";
    
    export default class extends Controller {
    
      connect() {
        // 'store' the uppy instance against your class instance
        this._uppy = new Uppy();
      }
    
      disconnect() {
        // read the instance and access its close method
        this._uppy.close();
      }
    
    }
    

    Option 2 - Store the variable globally

    Another way to think about this is that you do not want to create a new Uppy instance if there is already one created.

    You can store this instance on window, although this breaks the concept of your file being an isolated module.

    Another way is to store the instance in the module file itself, this may create some edge case issues for unit tests but see how you go.

    import { Controller } from "@hotwired/stimulus";
    import Uppy from "@uppy/core";
    
    // declare a variable in the 'module' scope (this file).
    let uppy;
    
    export default class extends Controller {
    
      connect() {
        // if uppy has already been instantiated - do not create a new one
        if (uppy) return;
        // 'store' the uppy instance against your module scoped global variable
        uppy = new Uppy();
      }
    
      disconnect() {
        // read the instance and access its close method
        uppy && uppy.close();
        // reset the variable to null
        uppy = null;
      }
    
    }