How to prevent custom component from reloading on every change?

As I edit my custom component, it keeps reloading on every little change, even every keystroke.

It is getting more than a little annoying! Can I turn this off?

Hey @bradlymathews!

Going to move this into the feature request section. At the moment, I'm not aware of a way to get what you're looking for outside of editing your code in a separate window and pasting it in, unfortunately.

@bradlymathews @Kabirdas I've noticed that one factor that contributes to unintentional reloads for custom components is a dynamic page layout change.

For example, if I have another component on the page that is dynamically shown / hidden, the custom component is completely reloaded (and any embedded dynamic data - ex., JS variables, event handlers - will be lost).

For example, in the attached screenshot, I have a container in the upper-left that dynamically appears when an inbound call is received from Twilio. When I click "Answer," a status value in the custom component's model (the block "log" box on the right side) is updated, (correctly) triggering window.Retool.subscribe. Simultaneously, that value change causes the "Answer / Decline" container to be hidden, which then triggers a complete refresh of the custom component, thereby losing the JS objects and event handlers that were instantiated in the custom component (and in this case causing the inbound call object to be destroyed and the call to be terminated).

To avoid this, I've temporarily turned off the show/hide condition on the other container and I'm going to further experiment with relocating the component on the page to see if specific positioning will help to prevent the reload of the component.

@jmikem ,

Good breakdown! This is not the same issue I am having (I am creating the CC and every keystroke I make while editing the html or js causing it to repaint itself and rerun all JS.)

But I have run into this before as well - trying to maintain state in a CC and sync with its parent app.

I have had success in some cases with storing the model, or parts of it, in a temp state. If needed you can also have the CC trigger a query (basically functioning as an event) that runs a query in parent app which updates the temp state.

1 Like

Thanks for the feedback @jmikem!

This seems similar to the request for a more persistent custom component model here. It sounds like there may also be some JavaScript objects you're declaring within the component itself that you'd like to persist - how exactly are you defining those?

Hi @Kabirdas, yes, I think this lines up well with the request that you linked.

A good example of what I'm doing is with an extension of Evan Weiss' Twilio Voice example. In the snippet below, I declare a few global variables in the custom component's iframe code (within the tag).

For example, "inc_connection" is intended to hold a Twilio call object when an inbound call event is raised a Twilio device object (named "device" in the iframe script's global vars). The call object is generated from "device" object's "on incoming" event; when that event is triggered, the model state's "status" field is updated to "call_incoming" and a "new call" container appears in the page that allows the user to click "Accept" and "Reject" buttons. If the user clicks "Accept," the model state's "status" field is changed to "call_incoming_answered" and the "new call" container is hidden on the page and a separate "Active Call" container component is shown in the parent Retool page (controlled by the 'answerNewCall' query). If the dynamic display / hiding of the containers causes the custom component to change its location on the page, the entire custom component's DOM is destroyed, including the active inc_connection call object and the device object, along with the event handlers that were attached to those objects.

(I'll note here that I've also seen cases where custom components contained within modules that are then placed within parent pages do not render in Preview mode if the custom component and/or custom component's containing module are hidden, so data exposed by the custom component isn't loaded and made available to the top-level parent Retool page - this gets particularly tricky because it loads in Edit mode but not in Preview mode. I note this as another - though possibly unrelated - example of display rendering / layout having an impact on custom component behavior.)

If possible, it would be great to be able to adjust the appearance or location of the custom component without reloading its entire DOM and losing any iframe-script-defined data contained within.

<script>

      // Initialize variables to be used inside of the window.Retool.subscribe function
      var device;
      var currentPhoneNumber;
      var inc_connection;
      
      // window.Retool.subscribe is evaluated whenever the Retool custom component model changes: https://docs.retool.com/docs/custom-components#non-react-javascript
      window.Retool.subscribe(async function (model) {
        // subscribes to model updates
        // all model values can be accessed here
        if (model.data && model.data.token && !device) { // If a voice token is passed into the model, and the device object is not yet available...
          device = new Twilio.Device(model.data.token); // Instantiate a new Device: https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice#instantiate-a-device

          // Set event handlers on the new Device which trigger log messages: https://www.twilio.com/docs/voice/sdks/javascript/twiliodevice#deviceoneventname-listener
          
          device.on("incoming", function (conn) {                        
            log("Incoming connection from " + conn.parameters.From);
            inc_connection = conn;
            
            inc_connection.on("accept", (call) => {              
              window.Retool.triggerQuery('answerNewCall');
            });
            
            var updatedModel = JSON.parse(JSON.stringify(model));
            updatedModel.callerId = conn.parameters.From;
            updatedModel.status = 'call_incoming';
            window.Retool.modelUpdate(updatedModel);
            window.Retool.triggerQuery('openNewCall');
          });
          
          device.on("registered", function() {
            log('The device is ready to receive calls.');
          });
          
          device.register();
          
        } // END initial IF
        
        if(model.status === "call_incoming_answered"){
          inc_connection.accept(); 
        }
        
      }); // END OF Model subscribe
    </script>