Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save ivanzamarro/6fa9d26dbf7da135eb142a22f455b9b3 to your computer and use it in GitHub Desktop.

Select an option

Save ivanzamarro/6fa9d26dbf7da135eb142a22f455b9b3 to your computer and use it in GitHub Desktop.
React Patterns - Imperative Widget

General Description

This pattern focuses on implementing reusable widgets in React that are exposed via an imperative API. Using useImperativeHandle, React's internal components can be controlled directly from the outside. This approach is ideal when a component needs to be managed outside React's normal flow, such as in applications integrating third-party systems or components with customized lifecycles.

In this case, we will document a general pattern for externally controllable widgets, using a simple example that demonstrates how to implement this functionality with an injectable key.


Pattern: Simple Imperative Widget

Widget Structure

  1. Singleton Definition:

    • Ensures only one instance of the widget exists.
    • Allows the creation, rendering, and disposal of the widget through a clear API.
    • Supports injecting a custom key to avoid conflicts in the global space.
  2. React Component:

    • Implements the main functionality of the widget, exposing its API through useImperativeHandle.
  3. Custom Hook (optional):

    • Encapsulates logic and state, separating business logic from presentation.

Implementation Example

index.js (Widget Definition)

import * as React from 'react';
import { createRoot } from 'react-dom/client';
import SimpleWidget from './simple-widget';

const createImperativeWidget = (globalKey = '__WidgetInstance') => {
    let instance = window[globalKey] || null;

    function createInstance(containerElement) {
        let ref = { current: null };
        let rootInstance = createRoot(containerElement);

        function render() {
            if (!ref.current) {
                ref = React.createRef();
                rootInstance.render(<SimpleWidget ref={ref} />);
            }
        }

        function dispose() {
            rootInstance.unmount();
            instance = null;
            window[globalKey] = null;
        }

        return {
            application: () => ref.current,
            render,
            dispose
        };
    }

    return {
        getInstance: (containerElement) => {
            if (!instance && containerElement) {
                instance = createInstance(containerElement);
                window[globalKey] = instance;
            }
            return instance;
        }
    };
};

export default createImperativeWidget;

simple-widget.jsx (React Component)

import * as React from 'react';

function SimpleWidget(_, ref) {
    const [count, setCount] = React.useState(0);

    React.useImperativeHandle(ref, () => ({
        increment: () => setCount((prev) => prev + 1),
        decrement: () => setCount((prev) => prev - 1),
        reset: () => setCount(0)
    }));

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
            <button onClick={() => setCount((prev) => prev - 1)}>Decrement</button>
        </div>
    );
}

export default React.forwardRef(SimpleWidget);

Usage

Custom Key Injection

import createImperativeWidget from './index';

// Create a widget instance with a custom global key
const WidgetFactory = createImperativeWidget('__CustomWidgetKey');

// Render the widget
const container = document.getElementById('widget-container');
const widget = WidgetFactory.getInstance(container);
widget.render();

// Use exposed methods
widget.application().increment();
widget.application().decrement();
widget.application().reset();

Benefits

  • Flexibility: Key injection allows the widget to integrate into global contexts without collisions.
  • Reusability: This pattern is reusable for different widgets and use cases.
  • Simplicity: Maintains an intuitive API, easy to use even outside the React context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment