Reference Attribute Editor Pattern
Overview
A widget that manages a single Reference type attribute via a dropdown selector. The widget reads other attributes from editedValues to filter the available options (e.g., only show server details that match the selected component's architecture). When the user selects an option, onAttributeEdit is called with a referenceValues payload, and the selection is included in the main form's save.
When to Use
- A Reference attribute needs a filtered/contextual dropdown (not just "all instances of type X")
- Options must be narrowed based on other attributes already set on the form
- The widget is on a Create or Edit layout, and the reference selection should be saved with the main form
Applicable Layout Types
| Target | Notes |
|---|---|
CreateLayout | Most common — contextual filtering based on other new attribute values |
EditLayout | Also works for editing existing reference selections |
Pattern Structure
- Read related attribute values from
editedValuesto determine filter criteria (e.g., an environment ID from a reference field) - Fetch candidate instances via
useProtrakApiusing those filter criteria - Map API results to
{ label, value }dropdown options - On dropdown selection, call
onAttributeEditwith{ type: 'Reference', referenceValues: [{ id, name }] } - (Optional) Pre-populate the selection when
cloneSourceInstanceis present or when existingeditedValuesalready contain a reference
Code Example
function RelatedServerDetailsInfoWidget(pageContext) {
const { protrakComponents, protrakUtils } =
React.useContext(customWidgetContext);
const { H3, Dropdown, Container, Spinner, ErrorModal } = protrakComponents;
const { useProtrakApi } = protrakUtils;
const { editedValues, onAttributeEdit } = pageContext;
const ATTR_NAME = 'ServerDetailsToServerDetailsRef';
const [options, setOptions] = React.useState([]);
const [selected, setSelected] = React.useState([]);
// Read the parent environment reference from editedValues
const environmentId =
editedValues?.EnvironmentToServerDetailsRef?.referenceValues?.[0]?.id;
// Read the component filter from editedValues
const selectedComponents =
editedValues?.ComponentMultiselect?.arrayValue || [];
// Fetch server details related to the selected environment
const fetchServerDetails = React.useCallback(({ envId }) => {
if (!envId) return null;
return {
endpoint: `instances/${envId}/relatedInstances`,
config: {
method: 'GET',
params: {
'attributes[0]': 'Name',
'attributes[1]': 'TypeOfArchitecture',
'attributes[2]': 'ComponentMultiselect',
// Filter: only Primary architecture
'AttributeFilterExpressions[0].AttributeFilterConditions[0].AttributeName':
'TypeOfArchitecture',
'AttributeFilterExpressions[0].AttributeFilterConditions[0].Condition':
'Equals',
'AttributeFilterExpressions[0].AttributeFilterConditions[0].FirstValue':
'Primary',
'AttributeFilterExpressions[0].AttributeFilterConditions[0].Operator':
'None',
'AttributeFilterExpressions[0].Operator': 'None',
'RelationFilters[0].InstanceId': envId,
'RelationFilters[0].TypeName': 'HardwareSizing',
'RelationFilters[0].RelationTypeName': 'EnvironmentToHardwareSizing',
'RelationFilters[0].RelationDirection': 'To',
getAllowedOperations: true,
skip: 0,
take: 10000,
},
},
};
}, []);
const { state: serverDetailsResponse } = useProtrakApi({
requestConfig: fetchServerDetails,
envId: environmentId,
});
React.useEffect(() => {
if (
serverDetailsResponse.isFulfilled &&
serverDetailsResponse.data?.items
) {
// Filter by component compatibility
const compatible = serverDetailsResponse.data.items.filter((item) => {
const itemComponents =
item.attributes?.find((a) => a.name === 'ComponentMultiselect')
?.arrayValue || [];
return selectedComponents.every((c) => itemComponents.includes(c));
});
setOptions(
compatible.map((item) => ({
label: item.name,
value: item.id,
}))
);
}
}, [serverDetailsResponse, selectedComponents]);
const handleSelect = (selectedOption) => {
if (!selectedOption) return;
setSelected([selectedOption]);
// Push the reference selection into the parent form's dirty state
onAttributeEdit(
ATTR_NAME,
{
name: ATTR_NAME,
type: 'Reference',
canUpdate: true,
referenceValues: [
{ id: selectedOption.value, name: selectedOption.label },
],
},
''
);
};
if (serverDetailsResponse.isLoading) {
return (
<Container>
<Spinner />
</Container>
);
}
if (serverDetailsResponse.isError) {
return <ErrorModal message="Error loading server details..." />;
}
return (
<div style={{ padding: '8px 12px' }}>
<H3 style={{ padding: '0px 0.8rem' }}>Select Server Details</H3>
<div style={{ padding: '8px 12px' }}>
<div
style={{ paddingBottom: '7px', fontSize: '12px', fontWeight: '600' }}
>
Server Details
</div>
<Dropdown
defaultValues={selected}
isMultiselect={false}
picklistOptions={options}
onValueChange={handleSelect}
/>
</div>
</div>
);
}
onAttributeEdit for Reference Attributes
// Single reference
onAttributeEdit(
'MyReferenceAttribute',
{
name: 'MyReferenceAttribute',
type: 'Reference',
canUpdate: true,
referenceValues: [{ id: 'instance-guid', name: 'Instance Display Name' }],
},
''
);
// Clear the reference (set to empty)
onAttributeEdit(
'MyReferenceAttribute',
{
name: 'MyReferenceAttribute',
type: 'Reference',
canUpdate: true,
referenceValues: [],
},
''
);
Reacting to Other Form Field Changes
editedValues updates reactively as the user edits other fields. Use it directly — no need for a useEffect to track changes:
// This reference is re-evaluated on each render as editedValues updates
const environmentId =
editedValues?.EnvironmentToServerDetailsRef?.referenceValues?.[0]?.id;
// Pass it as a dependency to useProtrakApi — the API call re-fires when it changes
const { state } = useProtrakApi({
requestConfig: fetchConfig,
envId: environmentId,
});
Real Examples
RelatedServerDetailsInfoWidget.js— Protrak-Implementation