|
| 1 | +# Custom Form Elements |
| 2 | + |
| 3 | +Sometimes, the [form elements](./forms.md) ReVISit provides are not sufficient for your needs. In this case, you can create your own custom form elements. Your custom form elements can be coded in React and can use any React libraries you want. You can then include these custom form elements in your study config and they will be rendered in the study just like the built-in form elements. |
| 4 | + |
| 5 | +This page will walk you through how to create and use custom form elements in your ReVISit study, and show you how to provide custom validation logic for your custom form elements. |
| 6 | + |
| 7 | +## Creating Custom Form Elements |
| 8 | + |
| 9 | +To create a custom form element, you need to define a React component that follows the structure defined by the `CustomResponseParams` type. This component receives the current response value, metadata about the response itself, optional custom parameters from the study config, the current validation error message if one exists, and a `field` helper for updating and blurring the value. You can then use this component in your study configuration to collect responses from participants. Here's a basic example of a custom form element that collects a color input from participants: |
| 10 | + |
| 11 | +```tsx title="ColorPicker.tsx" |
| 12 | +import { CustomResponseParams } from '../../../store/types'; |
| 13 | + |
| 14 | +type ColorPickerValue = string; |
| 15 | + |
| 16 | +export default function ColorPicker({ |
| 17 | + response, |
| 18 | + value, |
| 19 | + error, |
| 20 | + disabled, |
| 21 | + field, |
| 22 | +}: CustomResponseParams<Record<string, unknown>, ColorPickerValue>) { |
| 23 | + const inputProps = field.getInputProps(); |
| 24 | + |
| 25 | + return ( |
| 26 | + <> |
| 27 | + <p>{response.prompt}</p> |
| 28 | + <input |
| 29 | + type="color" |
| 30 | + {...inputProps} |
| 31 | + value={typeof value === 'string' ? value : '#000000'} |
| 32 | + disabled={disabled} |
| 33 | + onChange={(event) => field.setValue(event.currentTarget.value)} |
| 34 | + onBlur={() => field.onBlur()} |
| 35 | + /> |
| 36 | + {error && <p>{error}</p>} |
| 37 | + </> |
| 38 | + ); |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +In this example, the `ColorPicker` component renders an HTML color input. The `getInputProps` function is useful for wiring a native input, while `setValue` updates the stored answer and `onBlur` marks the field as interacted with. For more custom interfaces, you can call `setValue` and `onBlur` directly without relying much on `getInputProps`. |
| 43 | + |
| 44 | +## Using Custom Form Elements in Your Study Config |
| 45 | + |
| 46 | +To use your custom form element in your study config, you need to define a response with `type` set to `custom` and provide the path to your custom form element component. Custom responses live inside a component's `response` array, just like built-in response types. Here's how you can include the `ColorPicker` custom form element in your study config: |
| 47 | + |
| 48 | +```json |
| 49 | +{ |
| 50 | + "type": "questionnaire", |
| 51 | + "response": [ |
| 52 | + { |
| 53 | + "id": "favoriteColor", |
| 54 | + "prompt": "What is your favorite color?", |
| 55 | + "location": "belowStimulus", |
| 56 | + "type": "custom", |
| 57 | + "path": "my-study/assets/ColorPicker.tsx", |
| 58 | + "default": "#ff0000" |
| 59 | + } |
| 60 | + ] |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +::note[Note] |
| 65 | +The path here is relative to the `src/public` directory of the ReVISit app, not the `public` directory used for most other study assets. You can place your custom form element component anywhere under `src/public`. We recommend that you follow the same folder structure that we suggest in the [react stimulus docs](./react-stimulus.md) for your custom form elements. |
| 66 | +::: |
| 67 | + |
| 68 | +::note[JSON-serializable values] |
| 69 | +Custom response values must be JSON-serializable. This applies both to the values you store through `field.setValue(...)` and to any `default` value you define in the study config. Strings, numbers, booleans, `null`, arrays, and plain objects are supported. |
| 70 | +::: |
| 71 | + |
| 72 | +## Custom Validation Logic for Custom Form Elements |
| 73 | + |
| 74 | +Custom validation logic can be added to your custom form elements by defining a validation function that checks the response value and returns an error message if the value is invalid. The validation controls whether the participant can submit their response or not. The `validate` function receives the current value, the full set of response values for the component, and the current response config, so it can validate against sibling answers or `response.parameters`. Here's an example of how you can add custom validation logic to the `ColorPicker` component: |
| 75 | + |
| 76 | +```tsx title="ColorPickerWithValidation.tsx" |
| 77 | +import { CustomResponseParams, CustomResponseValidate } from '../../../store/types'; |
| 78 | + |
| 79 | +type ColorPickerValue = string; |
| 80 | + |
| 81 | +export default function ColorPicker({ |
| 82 | + response, |
| 83 | + value, |
| 84 | + error, |
| 85 | + disabled, |
| 86 | + field, |
| 87 | +}: CustomResponseParams<Record<string, unknown>, ColorPickerValue>) { |
| 88 | + const inputProps = field.getInputProps(); |
| 89 | + |
| 90 | + return ( |
| 91 | + <> |
| 92 | + <p>{response.prompt}</p> |
| 93 | + <input |
| 94 | + type="color" |
| 95 | + {...inputProps} |
| 96 | + value={typeof value === 'string' ? value : '#000000'} |
| 97 | + disabled={disabled} |
| 98 | + onChange={(event) => field.setValue(event.currentTarget.value)} |
| 99 | + onBlur={() => field.onBlur()} |
| 100 | + /> |
| 101 | + {error && <p>{error}</p>} |
| 102 | + </> |
| 103 | + ); |
| 104 | +} |
| 105 | + |
| 106 | +export const validate: CustomResponseValidate<ColorPickerValue> = (value, _values, response) => { |
| 107 | + if (typeof value !== 'string' || !value) { |
| 108 | + return `Please select a color for "${response.prompt}".`; |
| 109 | + } |
| 110 | + |
| 111 | + return null; |
| 112 | +}; |
| 113 | +``` |
| 114 | + |
| 115 | +In this example, we supplied a `validate` function that checks if a color has been selected. If no color is selected, it returns an error message prompting the participant to select a color. If a color is selected, it returns `null`, indicating that the response is valid. The function must be called `validate` and must be exported from the same file as the custom form element component. |
| 116 | + |
| 117 | +When you include this custom form element in your study config, the validation logic will be automatically applied, and participants will not be able to submit their response until they have selected a color. |
| 118 | + |
| 119 | +## Provenance tracking for custom form elements |
| 120 | + |
| 121 | +Since custom form elements are hooked into the same response system as built-in form elements, their answers participate in the standard saving and provenance flow. To keep the stored answer in sync, update values through `field.setValue(...)`. Call `field.onBlur()` when the user finishes interacting with the element so validation and touched-state behavior work as expected. |
0 commit comments