Skip to main content

Introducing Workflow Engine, try for FREE workflowengine.io.

Custom Component Wrapper

Overview​

A custom component wrapper is a React component that wraps the entire form viewer, allowing you to inject additional functionality, context providers, or styling at the root level of your form. Wrappers are useful for:

  • Applying theme providers (Material UI, React Suite, etc.)
  • Adding localization contexts
  • Providing custom styling or CSS baselines
  • Injecting global state providers
  • Handling language-specific layout (RTL/LTR)

Creating a Wrapper Component​

A wrapper component have to implement the FormViewerWrapper type, which is a React component accepting FormViewerWrapperComponentProps`:

import {createTheme, ScopedCssBaseline, ThemeProvider} from '@mui/material'
import type {ThemeOptions} from '@mui/material/styles/createTheme'
import type {FormViewerWrapper, Language} from '@react-form-builder/core'
import {useBuilderTheme} from '@react-form-builder/core'
import {useMemo} from 'react'

type Theme = 'dark' | 'light'

const createMuiTheme = (theme: Theme, language?: Language) => {
const options: ThemeOptions = {
cssVariables: true,
palette: {
mode: theme,
}
}
return createTheme(options)
}

const containerStyle = {
height: '100%',
width: '100%'
}

export const MuiViewerWrapper: FormViewerWrapper = ({children, language}) => {
const theme = useBuilderTheme()
const muiTheme = useMemo(() => createMuiTheme(theme, language), [language, theme])

return <ThemeProvider theme={muiTheme}>
<ScopedCssBaseline style={containerStyle}>{children}</ScopedCssBaseline>
</ThemeProvider>
}

Required Props​

  • language: The current language object containing fullCode (e.g., 'en-US') and bidi (BiDi.LTR or BiDi.RTL)
  • children: The form viewer content to be wrapped

Registering a Wrapper​

Wrappers are registered using the withViewerWrapper method on a View instance:

import {createView} from '@react-form-builder/core'
import {myComponents} from './my-components'
import {MyWrapper} from './MyWrapper'

const view = createView(myComponents)
.withViewerWrapper(MyWrapper)

Multiple Wrappers​

You can chain multiple wrappers, and they will be nested in the order they are added:

const view = createView(components)
.withViewerWrapper(ThemeWrapper)
.withViewerWrapper(LocalizationWrapper)
.withViewerWrapper(ErrorBoundaryWrapper);

The nesting order will be:

<ThemeWrapper>
<LocalizationWrapper>
<ErrorBoundaryWrapper>
{form}
</ErrorBoundaryWrapper>
</LocalizationWrapper>
</ThemeWrapper>

Examples​

Simple CSS Wrapper​

import type {FormViewerWrapper} from '@react-form-builder/core'

export const CssVariablesWrapper: FormViewerWrapper = ({children}) => {
return (
<div
style={{
'--primary-color': '#007bff',
'--secondary-color': '#6c757d',
height: '100%',
width: '100%',
padding: 25,
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
borderRadius: 16,
border: '1px solid rgba(255, 255, 255, 0.3)',
borderColor: 'var(--primary-color)',
boxShadow: '0 4px 30px var(--secondary-color)',
outline: '1px solid rgba(255, 255, 255, 0.1)',
outlineOffset: -5,
color: '#fff',
} as any}
>
{children}
</div>
)
}

Live Example​

Live Editor
function App() {
  const CssVariablesWrapper = ({children}) => {
    return (
      <div
        style={{
          '--primary-color': '#007bff',
          '--secondary-color': '#6c757d',
          height: '100%',
          width: '100%',
          padding: 25,
          background: 'rgba(255, 255, 255, 0.1)',
          backdropFilter: 'blur(10px)',
          borderRadius: 16,
          border: '1px solid rgba(255, 255, 255, 0.3)',
          borderColor: 'var(--primary-color)',
          boxShadow: '0 4px 30px var(--secondary-color)',
          outline: '1px solid rgba(255, 255, 255, 0.1)',
          outlineOffset: -5,
          color: '#fff',
        }}
      >
        {children}
      </div>
    )
  }

  const view = useMemo(() => {
    const result = createView([...muiView.all()])
    result.withViewerWrapper(CssVariablesWrapper)
    return result
  }, [])

  const formJson = {
    "form": {
      "key": "Screen",
      "type": "Screen",
      "children": [
        {
          "key": "name",
          "type": "MuiTextField"
        }
      ]
    }
  }

  return (
    <FormViewer
      view={view}
      getForm={() => JSON.stringify(formJson)}
    />
  )
}
Result
Loading...

Best Practices​

  1. Keep Wrappers Focused: Each wrapper should have a single responsibility (theme, localization, error handling, etc.).
  2. Performance: Use useMemo or React.memo for expensive calculations in wrappers that might re-render frequently.
  3. Order Matters: Add wrappers in the order they should be nested. Context-dependent wrappers (like theme) should be inside wrappers that might affect them.
  4. Height and Width: Ensure your wrapper sets height: '100%' and width: '100%' to properly contain the form.
  5. Type Safety: Always import and use the FormViewerWrapper type for better TypeScript support.
  6. Language Awareness: Use the language prop to adjust RTL/LTR layouts or apply locale-specific settings.

Common Use Cases​

Theme Integration​

Wrap your form with theme providers from popular UI libraries:

  • Material UI ThemeProvider
  • React Suite CustomProvider

Localization​

Apply locale settings for date pickers, number formatting, and text direction.

Error Boundaries​

Wrap forms with error boundaries to catch and handle rendering errors gracefully.

Custom Styling​

Apply global CSS variables, custom fonts, or brand-specific styling.

Troubleshooting​

Wrapper Not Applied​

  • Ensure you call withViewerWrapper on the correct view instance
  • Verify the wrapper component is properly exported and imported
  • Check that the wrapper returns valid JSX with children rendered

Styling Issues​

  • Ensure your wrapper sets height: '100%' and width: '100%'
  • Check for CSS conflicts between nested wrappers
  • Verify theme providers are properly configured

Performance Problems​

  • Wrap expensive calculations in useMemo
  • Avoid unnecessary re-renders with React.memo
  • Keep wrapper logic minimal and focused