User defined properties
User-defined properties let you override component properties at runtime, bypassing the static and computed values that FormEngine Core normally calculates. You can change properties through actions or directly in React code using userDefinedProps and setUserDefinedProps.
In this guide, you'll learn:
- What user-defined properties are and when they're useful
- How to use
setUserDefinedPropsin actions to change component behavior - How to read and modify
userDefinedPropsin React components - Best practices and precautions for this advanced feature
Overview
User-defined properties are a runtime override mechanism. When you set a property via setUserDefinedProps, it takes precedence over:
- Static property values from the form JSON
- Computed or localized properties
- Default values defined in the component's metadata
This is an advanced feature that can solve specific integration challenges, but it should be used sparingly. Overusing user-defined properties can make your form logic harder to understand and maintain.
How user-defined properties work
Every component in FormEngine Core has a userDefinedProps field (type Record<string, any>) in its ComponentData. When this
field contains values, they're merged with (and override) the component's regular properties before rendering.
You can modify userDefinedProps in two ways:
- From actions: Use
e.setUserDefinedProps(props)inside an action handler, whereeis an ActionEventArgs instance. - From React components: Access
componentData.userDefinedPropsdirectly via the useComponentData hook.
The override hierarchy
Properties are resolved in this order (later overrides earlier):
- Default values from the component's
define()metadata - Static values from the form JSON (
props.value) - Computed values (
props.computeType: "function") - Localized values (
props.computeType: "localization") - User-defined properties (
userDefinedProps)
If a property exists in userDefinedProps, that value wins, even if it contradicts validation rules, computed logic, or form data binding.
Using setUserDefinedProps in actions
The most common use case is modifying component properties from an action handler. Actions can run in response to events like onClick,
onDidMount, or onChange.
Example: Conditional styling based on validation
Change a button's color based on form validity:
{
"key": "submitBtn",
"type": "MuiButton",
"props": {
"children": {
"value": "Submit"
},
"variant": {
"value": "contained"
}
},
"events": {
"onClick": [
{
"name": "validate",
"type": "common"
},
{
"name": "updateButtonStyle",
"type": "code"
}
]
}
}
Action implementation:
/**
* @param {ActionEventArgs} e - the action arguments.
* @param {} args - the action parameters arguments.
*/
async function updateButtonStyle(e, args) {
const hasErrors = e.store.form.componentTree.hasErrors
const newColor = hasErrors ? 'error' : 'success'
e.setUserDefinedProps({color: newColor})
}
Using userDefinedProps in React components
Sometimes you need to modify properties directly from a React component, especially when building custom form components.
Accessing the current component's properties
Use the useComponentData hook to get the component's data context:
import {useComponentData} from '@react-form-builder/core'
import {useCallback} from 'react'
const CustomToggle = ({toggled}: any) => {
const componentData = useComponentData()
const handleToggle = useCallback(() => {
const currentState = toggled ?? false
// Update user-defined properties
componentData.userDefinedProps = {
...componentData.userDefinedProps,
toggled: !currentState
}
}, [componentData, toggled])
return (
<button onClick={handleToggle}>
{toggled ? 'ON' : 'OFF'}
</button>
)
}
Modifying another component's properties
You can also modify properties of a different component by finding it in the component tree:
import {useComponentData} from '@react-form-builder/core'
import {useCallback} from 'react'
const PasswordVisibilityToggle = ({passwordInputKey}: any) => {
const componentData = useComponentData()
const {root} = componentData
const togglePasswordVisibility = useCallback(() => {
if (!passwordInputKey) return
const passwordField = root.findByKey(passwordInputKey)
if (!passwordField) return
// This way you can get the properties of the component
const componentProps = passwordField.componentState.propsWithoutChildren
const isHidden = componentProps.type === 'password'
const newType = isHidden ? 'text' : 'password'
passwordField.userDefinedProps = {
...passwordField.userDefinedProps,
type: newType
}
}, [passwordInputKey, root])
return (
<button onClick={togglePasswordVisibility}>
Toggle password visibility
</button>
)
}
Live examples
Dynamic label based on API data
This example shows a button that fetches weather data and updates its label.
Live example
function App() { const form = { tooltipType: 'MuiTooltip', errorType: 'MuiErrorWrapper', form: { key: 'Screen', type: 'Screen', children: [ { key: 'weatherButton', type: 'MuiButton', props: { children: {value: 'Get weather'}, variant: {value: 'contained'} }, events: { onClick: [ { name: 'fetchWeather', type: 'custom' } ] } } ] } } const getForm = useCallback(() => JSON.stringify(form), [form]) const actions = { fetchWeather: async function(e) { try { // Simulate API call await new Promise(resolve => setTimeout(resolve, 500)) const temperature = Math.floor(Math.random() * 30) + 10 e.setUserDefinedProps({ children: `Weather: ${temperature}°C`, color: temperature > 25 ? 'error' : 'primary' }) } catch { e.setUserDefinedProps({ children: 'Weather unavailable', color: 'inherit' }) } } } return <FormViewer view={muiView} getForm={getForm} actions={actions}/> }
Form with real-time character counter
A text field with a dynamic helper text showing character count.
Live example
function App() { const [actions] = useState({ updateCharCount(e) { const text = String(e.value || '').trim() const count = text.length const maxLength = 100 let helperText = `${count}/${maxLength} characters` let color = 'primary' if (count > maxLength) { helperText = `⚠️ ${count - maxLength} characters over limit` color = 'error' } else if (count > maxLength * 0.8) { helperText = `⚠️ ${maxLength - count} characters left` color = 'warning' } e.setUserDefinedProps({ helperText, color }) } }) const form = useMemo(() => ({ tooltipType: 'MuiTooltip', errorType: 'MuiErrorWrapper', form: { key: 'Screen', type: 'Screen', children: [ { key: 'comments', type: 'MuiTextField', props: { label: {value: 'Comments'}, multiline: {value: true}, rows: {value: 3} }, events: { onChange: [ { name: 'updateCharCount', type: 'custom' } ] } } ] } }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) return <FormViewer view={muiView} getForm={getForm} actions={actions}/> }
Best practices
- Use sparingly: User-defined properties are an escape hatch. Prefer computed properties, conditional rendering, or proper data binding when possible.
- Document overrides: When you use
setUserDefinedProps, add comments explaining why the override is necessary. - Merge, don't replace: When updating
userDefinedPropsin React components, spread existing properties to avoid losing other overrides:// Good
componentData.userDefinedProps = {
...componentData.userDefinedProps,
newProp: value
}
// Bad (loses other overrides)
componentData.userDefinedProps = { newProp: value } - Test thoroughly: Overrides can have unexpected side effects with validation, data binding, and conditional rendering.
Precautions and warnings
User-defined properties bypass FormEngine Core's normal property resolution. This can lead to:
- Unexpected behavior when overrides conflict with validation rules
- Maintenance challenges because the true component state is split between JSON and runtime code
- Performance issues if overrides trigger excessive re-renders
Common pitfalls
- Circular updates: An action that sets
userDefinedPropsmight trigger another event that sets them again, creating an infinite loop. - Lost defaults: If you override a property and later remove the override, the component won't revert to its computed or default value
until you explicitly clear
userDefinedProps. - Serialization issues:
userDefinedPropsaren't part of the form JSON, so they won't persist if you serialize and deserialize the form state.
Summary
- User-defined properties let you override component properties at runtime via
userDefinedPropsandsetUserDefinedProps. - They take precedence over all other property sources (static, computed, localized, defaults).
- You can set them from actions using
e.setUserDefinedProps()or directly in React components. - This is an advanced feature—use it sparingly and document your overrides.
- Always merge with existing properties to avoid losing other overrides.
For more information: