Best Practices for Reusing Custom Component Code in Multiple Instances in Retool

Hello Retool Community,

I am currently working on a project where I have developed a custom component in Retool. This component is a numeric input field that allows users to input a number, which gets updated in the Retool model. I have successfully implemented and styled this component, and it's working great.

However, I've come across a challenge that I'm hoping to get some insights on. I need to use multiple instances of this custom component across different parts of my application. Ideally, I want to maintain a single codebase for this component to ensure consistency and ease of maintenance.

Here’s a brief overview of my component:

  • It’s an HTML input of type number.
  • Includes JavaScript for debouncing input changes and updating the Retool model.
  • Styled with CSS to fill the entire frame of the component.

I am considering a few options, like using JavaScript snippets or creating an external JavaScript module, but I'm not sure what the best approach would be in Retool. My main goal is to avoid duplicating the same HTML and JavaScript code for each instance of the component.

Questions:

  1. Does Retool support creating templates or reusable components that can be instantiated multiple times with a single codebase?
  2. If not, what are the recommended best practices for handling such scenarios? Would JavaScript snippets be the way to go, or is there a more efficient method?
  3. Are there any potential pitfalls or considerations I should be aware of when reusing component code in Retool?

Any advice, tips, or examples from your own experience would be greatly appreciated. I'm looking to implement a solution that is both efficient and maintainable.

Thank you in advance for your help!

Best regards,

heres the frame code

html, body { margin: 0; padding: 0; height: 100%; width: 100%; } #stepper { display: flex; align-items: center; height: 100%; width: 100%; justify-content: center; } input[type="number"] { width: 100%; height: 100%; border: none; padding: 0; margin: 0; }
<div id="stepper">
  <input id="numberInput" type="number" step="0.25" oninput="handleInputChange(event)" style="text-align: center;" />
</div>

<script>
  let currentNumber = 0;

  function debounce(func, delay) {
    let debounceTimer;
    return function() {
      const context = this;
      const args = arguments;
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => func.apply(context, args), delay);
    }
  }

  function updateDisplay() {
    document.getElementById("numberInput").value = currentNumber.toFixed(2);
    window.Retool.modelUpdate({ number: currentNumber });
  }

  function handleInputChange(event) {
    const newValue = parseFloat(event.target.value);
    if (!isNaN(newValue)) {
      currentNumber = newValue;
      debouncedUpdateDisplay();
    }
  }

  const debouncedUpdateDisplay = debounce(updateDisplay, 250); // 250 ms delay

  window.Retool.subscribe(function (model) {
    currentNumber = model.number || 0;
    debouncedUpdateDisplay();
  });

  // Initialize display
  debouncedUpdateDisplay();
</script>
body: html, body { margin: 0; padding: 0; height: 100%; width: 100%; } #stepper { display: flex; align-items: center; height: 100%; width: 100%; justify-content: center; } input[type="number"] { width: 100%; height: 100%; border: none; padding: 0; margin: 0; }
<div id="stepper">
  <input id="numberInput" type="number" step="0.25" oninput="handleInputChange(event)" style="text-align: center;" />
</div>

<script>
  let currentNumber = 0;

  function debounce(func, delay) {
    let debounceTimer;
    return function() {
      const context = this;
      const args = arguments;
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(() => func.apply(context, args), delay);
    }
  }

  function updateDisplay() {
    document.getElementById("numberInput").value = currentNumber.toFixed(2);
    window.Retool.modelUpdate({ number: currentNumber });
  }

  function handleInputChange(event) {
    const newValue = parseFloat(event.target.value);
    if (!isNaN(newValue)) {
      currentNumber = newValue;
      debouncedUpdateDisplay();
    }
  }

  const debouncedUpdateDisplay = debounce(updateDisplay, 250); // 250 ms delay

  window.Retool.subscribe(function (model) {
    currentNumber = model.number || 0;
    debouncedUpdateDisplay();
  });

  // Initialize display
  debouncedUpdateDisplay();
</script>
1 Like

Hi @jojo

Retool released very recently the beta version of their new custom component and I guess there's something you can exploit for your use-case:

Otherwise, I usually solve that issue by packaging the component into a single js file, that is responsible to create everything. It requires a bit of tooling, though.

Here you can find an example:

Hope this help.

1 Like

thanks! im a bit weary to use the beta feature as a big clinic is relying on this, as for the second approach any direction u can give me of hope to implement this
thanks!