I'm very much still learning Retool and if there are easier ways I hope somebody jumps in!
Because Retool doesn't support (I think) programmatically adding components via JS within the app there are two ways that I can think of accomplishing this (and probably more that I don't yet know about).
The first would be to use a JSON Schema Form component.
You would query the content of your Google Sheet and then use a transformer to build the form schema from your sheet data. In the transformer, you'd build out definitions for each of the elements that you want to be able to specify in your sheet.
Retool uses react-jsonschema-form behind the scene for the JSON Schema Form component. I'm still trying to suss out exactly how to define each element and my attempts at styling have so far fallen flat but maybe someone with a deeper knowledge can chime in.
Creating a the form schema with a transformer:
const uiConfig = {{ getConfigSheet.data }}
const schema = {
type: "object",
properties: {}
}
uiConfig.forEach((row) => {
switch (row.type) {
case 'input':
schema.properties[row.name] = {
type: "string",
title: row.label,
"ui:widget": "input",
"ui:classNames": "",
"ui:options": {
style: {
fontSize: row.fontSize,
color: row.color
}
}
}
break
case 'button':
schema.properties[row.name] = {
type: "null",
title: row.label,
"ui:widget": "button",
"ui:classNames": "",
"ui:options": {
style: {
fontSize: row.fontSize,
color: row.color
},
}
}
break
}
})
return schema
As you can see, the elements don't respect the color
style but do respect the fontSize
style. I have confirmed that you can use Bootstrap classes with the ui:classNames
property for styling. For instance, ui:classNames: "text-right"
will justify the label all the way to the right.
The other way would be writing a Custom Component that basically does the same thing, iterating over the rows and creating elements from the row definitions. You'd build your elements with JSX, which would be similar to how you would build a form in HTML/CSS.
Creating the form with a custom component:
import React from 'react'
import { type FC } from 'react'
import { Retool } from '@tryretool/custom-component-support'
interface ElementConfig {
name: string
type: string
label: string
fontSize: string
color: string
}
export const DynamicForm: FC = () => {
const [name, _setName] = Retool.useStateString({
name: 'name'
})
const [configSheetData] = Retool.useStateArray({
name: 'schema'
})
const renderComponent = (config: ElementConfig, index: number) => {
const { name, type, label, fontSize, color } = config
const style = {
fontSize: fontSize,
fontFamily: 'sans-serif',
color: color,
margin: '10px 0'
}
const labelStyle = {
paddingRight: '5px',
color: color
}
switch (type) {
case 'input':
return (
<div key={index} style={style}>
<label htmlFor={name} style={labelStyle}>
{label}
</label>
<input id={name} name={name} style={style} />
</div>
)
case 'button':
return (
<div key={index} style={style}>
<button id={name} name={name} style={style}>
{label}
</button>
</div>
)
}
}
const componentStyle = {
height: '100%',
backgroundColor: '#fff',
padding: '16px',
borderRadius: '8px',
border: '1px solid #e0e0e0',
overflow: 'hidden'
}
return (
<div className="dynamicUIForm" style={componentStyle}>
{configSheetData && configSheetData.map(renderComponent)}
</div>
)
}
Results in a dynamically-constructed list of components!
The benefit of this approach is also its curse: you have complete control over the form, its behavior, and the construction of the elements, but it also requires a lot more up front configuration/styling.
The JSON Schema Form is probably the best approach if you don't need to heavily style the components. If there's an easier way, I hope folks will point us in the right direction!