DEV Community

James Freund
James Freund

Posted on • Edited on

Next Experience: UI Renderer React

Precursor

ServiceNow originally put out their own react renderer for their early workspace components. This allowed them to develop complex components in React while they were still working on their own component design library.

Once their own component library was fully built out they removed the @servicenow/ui-renderer-react library from existence. I believe removing the react renderer was for a good reason and would have impacted the adoption of their new component system, but now its been a few years and that component system could use a content boost.

There are tons of amazing utilities and components built using React that cannot be used in now components simply because a react renderer didn't exist. If one did exist, it would allow us all to tap into thousands of useful components.

This was the main reason I originally built the @quixomatic/ui-renderer-react library. However, that solution required manual modifications to global ServiceNow CLI files, making it difficult to distribute and maintain across teams and projects.

The New Solution: I've now created @quixomatic/ui-renderer-react-simple - a React 18 renderer that requires zero global file modifications and provides one-command setup for any ServiceNow project.

The Problem We Solved

The original challenge wasn't just creating a React renderer - it was dealing with ServiceNow's babel plugin that transforms JSX. The plugin would detect React components but transform them to Snabbdom VNodes instead of React elements, causing the infamous "Objects are not valid as a React child" error.

Previous Solution: Required manually editing global babel plugin files in ~/.snc/.extensions/.

New Solution: Creates a local "fake" @servicenow/ui-renderer-react package that tricks the babel plugin into using proper React JSX transformation, plus automatically patches the babel plugin - no global modifications needed!

Installation (The Easy Way!)

The new solution provides one-command setup with automatic babel plugin patching:

# Install the package and React 18
npm install @quixomatic/ui-renderer-react-simple react@18 react-dom@18

# Run the automatic setup script (includes babel patching!)
npx setup-servicenow-react

# Complete the setup
npm install
Enter fullscreen mode Exit fullscreen mode

That's it! No manual file editing, no global modifications, no complex setup instructions. The setup script automatically handles everything including babel plugin patching.

What the Setup Does

The setup script automatically:

  1. Creates src/node_modules/@servicenow/ui-renderer-react/ directory
  2. Copies the React 18 renderer to masquerade as the official ServiceNow renderer
  3. Updates your package.json with the local dependency
  4. Ensures React 18 dependencies are present
  5. 🆕 Automatically patches ServiceNow's babel plugin for React support
  6. 🆕 Creates a backup of the original babel plugin for easy restoration

This tricks ServiceNow's babel plugin into thinking you have the official React renderer, so it uses React JSX transformation instead of Snabbdom.

CLI Commands

The new version includes convenient CLI commands:

# Complete setup (fake package + babel patch)
npx setup-servicenow-react

# Just patch the babel plugin (if already set up)
npx patch-servicenow-babel

# Restore the original babel plugin
npx restore-servicenow-babel
Enter fullscreen mode Exit fullscreen mode

Usage

With the new setup, your components use the exact same pattern as before, but now import from the "official" ServiceNow package:

Component Setup

import { createCustomElement } from "@servicenow/ui-core";
import react from "@servicenow/ui-renderer-react";  // Note: Now imports from @servicenow!
import view from "./view";
import styles from "./styles.scss";

createCustomElement("my-react-component", {
    renderer: { type: react },
    view,
    properties: {
        title: { default: "Hello React 18!" },
        count: { default: 0 }
    },
    actionHandlers: {
        INCREMENT: ({ state, updateProperties }) => {
            const { properties } = state;
            updateProperties({ count: properties.count + 1 });
        },
        DECREMENT: ({ state, updateProperties }) => {
            const { properties } = state;
            updateProperties({ count: properties.count - 1 });
        }
    },
    styles
});
Enter fullscreen mode Exit fullscreen mode

React Component with Modern Hooks

import React, { useState, useEffect, useCallback } from "react";

export default function MyReactComponent(state) {
    const { dispatch, properties } = state;
    const { title, count } = properties;

    // All React 18 hooks work perfectly!
    const [localState, setLocalState] = useState('');
    const [isLoading, setIsLoading] = useState(false);

    // useEffect works
    useEffect(() => {
        console.log('Component mounted with React 18!');
        return () => console.log('Cleanup on unmount');
    }, []);

    // useCallback works
    const handleIncrement = useCallback(() => {
        setIsLoading(true);
        dispatch('INCREMENT');
        setTimeout(() => setIsLoading(false), 300);
    }, [dispatch]);

    return (
        <div className="my-react-component">
            <h1>{title}</h1>
            <div className="counter">
                <button 
                    onClick={() => dispatch('DECREMENT')}
                    disabled={isLoading}
                >
                    -
                </button>
                <span className="count">{count}</span>
                <button 
                    onClick={handleIncrement}
                    disabled={isLoading}
                >
                    {isLoading ? '...' : '+'}
                </button>
            </div>
            <input 
                type="text"
                value={localState}
                onChange={(e) => setLocalState(e.target.value)}
                placeholder="Local React state works too!"
            />
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Hybrid Components (Snabbdom + React)

You can still mix Snabbdom parents with React children:

import { createCustomElement } from "@servicenow/ui-core";
import snabbdom from "@servicenow/ui-renderer-snabbdom";
import './my-react-component';  // Import the React component

const view = (state, { updateState }) => {
    const { showReact = false } = state;

    return (
        <div>
            <h1>Snabbdom Parent Component</h1>
            <button on-click={() => updateState({ showReact: !showReact })}>
                {showReact ? 'Hide' : 'Show'} React Component
            </button>

            {showReact && (
                <my-react-component title="Embedded React!" count={5} />
            )}
        </div>
    );
};

createCustomElement("hybrid-component", {
    renderer: { type: snabbdom },
    view
});
Enter fullscreen mode Exit fullscreen mode

New Features & Benefits

React 18 Support

  • ✅ All React 18 hooks (useId, useDeferredValue, useTransition, etc.)
  • ✅ Concurrent rendering features
  • ✅ Automatic batching
  • ✅ Improved performance with createRoot API

Zero Global Modifications (Automated!)

  • 🆕 Automatic babel plugin patching - no manual editing required
  • 🆕 Backup/restore functionality for babel plugin
  • 🆕 Cross-platform babel plugin detection (Windows/macOS/Linux)
  • ✅ No need to manually edit ServiceNow CLI files
  • ✅ Works with any ServiceNow CLI version
  • ✅ Team-friendly - no setup coordination needed
  • ✅ Works in CI/CD environments

Professional Distribution

  • ✅ Published on npm with semantic versioning
  • ✅ Automated testing across platforms
  • ✅ Comprehensive documentation and examples
  • ✅ Open source with contribution guidelines

Better Error Handling

  • ✅ Built-in React error boundaries
  • ✅ Proper error logging and reporting
  • ✅ Graceful degradation on component failures
  • 🆕 Graceful babel patching failure handling

Migration from Original Solution

If you're using the original @quixomatic/ui-renderer-react:

# Remove the old package
npm uninstall @quixomatic/ui-renderer-react

# Install the new solution
npm install @quixomatic/ui-renderer-react-simple react@18 react-dom@18

# Run the automated setup (includes babel patching!)
npx setup-servicenow-react
npm install

# Update your imports (only change needed!)
// Old
import react from "@quixomatic/ui-renderer-react";

// New  
import react from "@servicenow/ui-renderer-react";
Enter fullscreen mode Exit fullscreen mode

🆕 Key Migration Benefits:

  • No more manual babel file editing - everything is automated
  • Easy restoration - restore original babel plugin anytime with npx restore-servicenow-babel
  • Better reliability - automatic detection and patching across different system configurations

Your components will work exactly the same - just with React 18 features and zero manual configuration!

Babel Plugin Details

How It Works

The babel plugin patcher:

  1. Automatically locates ServiceNow's babel plugin file in ~/.snc/.extensions/ui-component/
  2. Creates a backup before making any changes
  3. Patches the React renderer configuration to use the correct module path instead of the faulty u() function
  4. Removes old renderer entries if they exist
  5. Verifies the patch was applied successfully

Manual Control

If you need manual control over the babel plugin:

# Patch just the babel plugin
npx patch-servicenow-babel

# Restore the original babel plugin
npx restore-servicenow-babel

# Verify current patch status
npx patch-servicenow-babel verify
Enter fullscreen mode Exit fullscreen mode

Troubleshooting Babel Issues

If babel patching fails:

# Check ServiceNow CLI installation
snc --version

# Try patching separately
npx patch-servicenow-babel

# If needed, restore and retry
npx restore-servicenow-babel
npx patch-servicenow-babel
Enter fullscreen mode Exit fullscreen mode

Example Repository

Check out the complete example repository which includes:

  • Basic Examples: Simple counter and form components
  • Advanced Examples: Complex forms with validation
  • Hook Examples: Demonstrating all React 18 hooks
  • Hybrid Examples: Mixing Snabbdom and React
  • Documentation: Complete setup and usage guides

Check out a full scaffolded project repository which includes nested react components using shadcn and tailwind within a parent snabbdom component.

Troubleshooting

The new solution eliminates most common issues, but if you encounter problems:

"Objects are not valid as a React child"

This usually means the setup didn't complete:

# Re-run the complete setup
npx setup-servicenow-react
npm install
Enter fullscreen mode Exit fullscreen mode

React Hooks Not Working

Ensure React 18 is installed:

npm list react react-dom
# Should show 18.x.x versions
Enter fullscreen mode Exit fullscreen mode

Babel Plugin Issues

If babel patching fails:

# Try patching separately
npx patch-servicenow-babel

# If that fails, check ServiceNow CLI
snc --version
Enter fullscreen mode Exit fullscreen mode

Import Errors

Make sure you're importing from the correct package:

import react from "@servicenow/ui-renderer-react";  // Correct
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this new solution, integrating React 18 into ServiceNow components is now truly effortless. The one-command setup with automatic babel plugin patching eliminates all complexity and manual configuration that made previous solutions difficult to distribute.

Key Improvements in v1.1.0:

  • 🚀 Automatic babel plugin patching - no manual editing required
  • 🔄 Backup/restore functionality for easy rollback
  • 🛡️ Cross-platform compatibility - works on Windows, macOS, and Linux
  • One-command setup - literally just npx setup-servicenow-react

This opens the door to using the entire React ecosystem within ServiceNow:

  • UI Libraries: Material-UI, Ant Design, Chakra UI, shadcn/ui
  • Utilities: React Hook Form, React Query, Zustand
  • Visualization: D3 React components, Chart libraries
  • And thousands more from the React community

The solution is production-ready, team-friendly, future-proof, and now completely automated. Go forth and be reactive - with React 18! 🚀


Package Information:

Top comments (5)

Collapse
 
vincepg13 profile image
Vincent Fisher

I've managed to get this working when using the snc cli locally. However when I attempt to deploy to the instance I always get 403 forbidden errors.

If I change the babel-plugin-jsx-now-pragmatic.js file back to the default ServiceNow one I can then upload to the instance but the react components no longer build or deploy.

Anyone had this issue?

Collapse
 
fauverism profile image
Robert Fauver

I love the concept but I'm having some trouble implementing. Is there anyway you can publish a repo with demo code of the snc ui-component?

Collapse
 
quixomatic profile image
James Freund

Just posted the example repository in the article. You can find it here.

Collapse
 
quixomatic profile image
James Freund

Yeah, I can put together a repo and post it on the article.

Collapse
 
chandralok_mishra_edb639f profile image
CHANDRALOK MISHRA

I am getting below error after I followed everything mentioned in the article.
Image description