Build custom components
Learn how to build your own components using React, or HTML and JavaScript.
This method for building custom components is deprecated. Use custom component libraries feature for developing 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.
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.
Name | Type | Parameter | Description |
---|---|---|---|
triggerQuery | function | string | Triggers the specified Retool query. |
model | JSON object | N/A | Represents the data to pass from your app to your custom component. |
modelUpdate | function | object | Updates 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.
<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.
<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>
{
"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.
<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>
{
"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 }}
.
<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.
<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>
SELECT
*
FROM
products
WHERE
name ILIKE {{ '%' + customcomponent1.model.name + '%' }}
{
"name": "%"
}
Quirks vs no-quirks rendering
Custom components are rendered inside of an iframe. When HTML documents, such as iframes, are rendered in a browser, this is done using quirks or no-quirks mode. The rendering behavior is slightly different with each mode.
Custom components are rendered in no-quirks mode by default. You can switch between modes on a per-custom component basis by toggling "Enable quirks rendering mode" in the IDE.