Skip to main content

Legacy custom components

Learn how to build your own components using React, or HTML and JavaScript.

info

See the custom component libraries beta documentation to learn about Retool's newer version of custom components.

If Retool's built-in components don't work for your use case, you can build your own custom component in React or JavaScript.

You may not need a custom component

Check Retool's Component Library before you start to build a custom component to see if any existing components are suitable. If your component serves a common use case, consider submitting a feature request to Retool.

Overview

A custom component consists of:

  • A place to put the HTML, CSS, and JavaScript which governs the appearance and behavior of the component. You enter this in the IFrame Code field of the Inspector, and Retool puts this code in an iFrame in the outer Retool app.
  • An interface for passing data back and forth between the Retool app and the custom component code.

You can also enable additional custom component features, such as the ability for your component to download files, in the Interaction menu of the Inspector.

Interface

The following methods and objects are used to pass data and state between your Retool app and your custom component. These methods are available for both React and non-React JavaScript custom components.

NameTypeParameterDescription
triggerQueryfunctionstringTriggers the specified Retool query.
modelJSON objectN/ARepresents the data to pass from your app to your custom component.
modelUpdatefunctionobjectUpdates the custom component's model to the specified fields. This allows the custom component to update its own state, which is available to the rest of the Retool app. You can think of modelUpdate as analogous to setState in React.

Connect interface for React components

Connect your React component to Retool by calling Retool.connectReactComponent(customComponentName). This exposes the triggerQuery, model, and modelUpdate variables. You can then access them in your component.

Connect interface for non-React components

For non-React components, Retool also exposes the subscribe method, in addition to triggerQuery, model, and modelUpdate.

To access triggerQuery, modelUpdate, and subscribe, use the window.Retool prefix.

function buttonClicked() {
window.Retool.triggerQuery("query1");
}

You can only access model and its keys within window.Retool.subscribe.

window.Retool.subscribe(function (model) {
console.log(model.name);
});

Minimum custom component with React

boilerplate.html below shows the minimum code needed to create a functional custom component with React.

boilerplate.html
<script
src="https://unpkg.com/react@18/umd/react.development.js"
crossorigin
></script>

<script
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
crossorigin
></script>

<div id="react"></div>
<script type="text/babel">
const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
<p>Hello, Retool!</p>
);
const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
const container = document.getElementById("react");
const root = ReactDOM.createRoot(container);
root.render(<ConnectedComponent />);
</script>

Minimum custom component with JavaScript

boilerplate_basic.html shows an example of a custom component without React.

boilerplate_basic.html
<html>
<body>
<script>
function updateName(e) {
window.Retool.modelUpdate({ name: e.target.value });
}
function buttonClicked() {
window.Retool.triggerQuery("query1");
}
window.Retool.subscribe(function (model) {
// subscribes to model updates
// all model values can be accessed here
document.getElementById("mirror").innerHTML = model.name || "";
});
</script>
<input onkeyup="updateName(event)" />
<button onclick="buttonClicked()">Trigger Query</button>
<div id="mirror"></div>
</body>
</html>
model.json
{
"name": "Default name"
}

Pass data to your custom component

To pass the data from components or queries to your custom component, set the data as the value of the custom component object in the Model field. In the following example, model.name updates whenever the value of textInput1 updates.

inflow.html
<script
src="https://unpkg.com/react@18/umd/react.development.js"
crossorigin
></script>

<script
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
crossorigin
></script>

<div id="react"></div>
<script type="text/babel">
const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
<p>Hello, {model.name}!</p>
);
const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
const container = document.getElementById("react");
const root = ReactDOM.createRoot(container);
root.render(<ConnectedComponent />);
</script>
model.json
{
"name": {{textInput1.value ? textInput1.value : 'World'}}
}

Pass data to your app from your custom component

You can also access data from your custom component in other components and queries in your app. In the following example code, modelUpdate sets the value of model.name to the custom component's Input value when it updates, similar to setState in React. The value can be accessed in your app using {{ customComponent.model.name }}.

outflow.html
<style>
body {
border: 5px solid red;
}
#react {
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script
src="https://unpkg.com/react@18/umd/react.development.js"
crossorigin
</script>
<script
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
crossorigin
</script>
<script src="https://unpkg.com/@material-ui/core@3.9.3/umd/material-ui.production.min.js"></script>
<div id="react"></div>
<script type="text/babel">
const { Input } = window["material-ui"];
const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
<Input
color="primary"
variant="outlined"
value={model.textInputValue}
onChange={(e) => modelUpdate({ name: e.target.value })}
/>
);
const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
const container = document.getElementById("react");
const root = ReactDOM.createRoot(container);
root.render(<ConnectedComponent />);
</script>

Trigger queries from your custom component

Use the triggerQuery function to trigger a query from your custom component.

In the following example, searchProducts is triggered when the user clicks the Search button and optionally enters a search term, handled by the onClick event listener of the Button component. The component's model and modelUpdate default to % in order to return all products when no search term is present.

triggerquery.html
<style>
body {
border: 5px solid red;
}
#react {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.button {
margin-left: 1em;
}
</style>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin>
</script

<script
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
crossorigin


></script

<script src="https://unpkg.com/@material-ui/core@3.9.3/umd/material-ui.production.min.js">
</script>
<div id="react"></div>
<script type="text/babel">
const { Input, Button } = window["material-ui"];
const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
<div>
<Input
color="primary"
variant="outlined"
value={model.textInputValue}
onChange={(e) =>
modelUpdate({ name: e.target.value ? e.target.value : "%" })
}
/>
<Button
className="button"
color="primary"
variant="outlined"
onClick={() => triggerQuery("searchProducts")}
>
Search
</Button>
</div>
);
const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
const container = document.getElementById("react");
const root = ReactDOM.createRoot(container);
root.render(<ConnectedComponent />);
</script>
searchProducts.sql
SELECT
*
FROM
products
WHERE
name ILIKE {{ '%' + customcomponent1.model.name + '%' }}
model.json
{
"name": "%"
}