Skip to content

Instantly share code, notes, and snippets.

@itsmunim
Created September 28, 2022 02:30
Show Gist options
  • Save itsmunim/0d114b1852a3c73edff2ef8f03fc2c50 to your computer and use it in GitHub Desktop.
Save itsmunim/0d114b1852a3c73edff2ef8f03fc2c50 to your computer and use it in GitHub Desktop.
A render helper for react components, supporting SSR too
import { createElement as e } from 'react';
import { createRoot, hydrateRoot } from 'react-dom/client';
/**
* All the root container refs have to be maintained for re-rendering purpose.
*/
const rootMap = {};
/**
* For id less root elements, this will generate a specific prefixed id.
*/
const randomId = ((i) => {
return () => {
return 'widget-root-' + ++i;
};
})(0);
const getContainer = (rootElement) => {
if (!rootElement) {
throw new Error('Invalid root element. Element id or element itself has to be provided');
}
const container = typeof rootElement === 'string' ? document.getElementById(rootElement) : rootElement;
container.id = container.id || randomId();
return container;
};
/**
* Helper method to render a widget instance created using `createWidgetInstance`
*
* @param widgetInstance Created widget instance using `createWidgetInstance`
* @param rootElement The root in dom, where this widget will be rendered. Can be an id or the element itself
* @param isSSR If the widget was rendered from server side, this will make sure it get hydrated properly on client side
*
* @example
*
* ```javascript
* const instance = createWidgetInstance(TabNavItem, {});
*
* renderWidgetInstance(instance, 'navbar');
* ```
*/
export const renderWidgetInstance = (widgetInstance, rootElement, isSSR) => {
const container = getContainer(rootElement);
let root = rootMap[container.id];
if (!root) {
root = isSSR ? hydrateRoot(container, widgetInstance) : createRoot(container);
}
root.render(widgetInstance);
rootMap[container.id] = root;
};
/**
* To unmount an widget instance from the root.
*
* @param rootElement The root in dom, where this widget will be rendered. Can be an id or the element itself
*
* @example
*
* ```javascript
* const instance = createWidgetInstance(TabNavItem, {});
* renderWidgetInstance(instance, 'navbar');
* ...
*
* unmount('navbar');
* ```
*/
export const unmount = (rootElement) => {
const container = getContainer(rootElement);
const root = rootMap[container.id];
root.unmount();
delete rootMap[container.id];
};
/**
* Helper function to create widget instance with props and children inside,
* which can then be passed as children to other widgets as well or even rendered separately.
* e.g. renderWidgetInstance
*
* @param widget A widget from `Widgets` js bundle exposed into `window`
* @param props The attributes to render the widget, refer widget specific reference
* @param children The children widgets/components we want to render inside this widget
* @returns The widget react element
*/
export const createWidgetInstance = (widget, props, children = null) => {
return e(widget, props, children);
};
/**
* Helper function to render the widgets in a non-react setup like a plain js based html, php etc.
*
* You need to call this everytime the passed `props` object has any changes. `ReactDOM` won't be
* re-rendering unless the values in `props` object is really changed, so no performance concerns.
*
* @param widget A widget from `Widgets` js bundle exposed into `window`
* @param props The attributes to render the widget, refer widget specific reference
* @param container The container ID in dom, where this widget will be rendered or even the element itself is acceptable
* @param isSSR If the widget was rendered from server side, this will make sure it get hydrated properly on client side
*
* @example
*
* ```javascript
* const {renderWidget, Header} = Widgets;
*
* const onSearch = (text) => {
* // do whatever you want
* }
* const props = {
* title: 'Amazing!',
* navLinks: [{title: 'Nav 1', href: '#link'}],
* onSearch
* };
*
* renderWidget(Header, props, 'header');
*
* // or
*
* renderWidget(Header, props, document.getElementsByClassName('header')[0]);
* ```
*/
export const renderWidget = (widget, props, container, isSSR = false) => {
renderWidgetInstance(createWidgetInstance(widget, props), container, isSSR);
};
/**
* Helper function to render the widgets in a non-react setup like a plain js based html, php etc.
*
* You need to call this everytime the passed `props` object has any changes. `ReactDOM` won't be
* re-rendering unless the values in `props` object is really changed, so no performance concerns.
*
* @param widget A widget from `Widgets` js bundle exposed into `window`
* @param props The attributes to render the widget, refer widget specific reference
* @param children The children widgets/components we want to render inside this widget
* @param container The container ID in dom, where this widget will be rendered or even the element itself is acceptable
* @param isSSR If the widget was rendered from server side, this will make sure it get hydrated properly on client side
*
* @example
*
* ```javascript
* const {renderWidgetWithChildren, createWidgetInstance, TabNav, TabNavItem} = Widgets;
*
* const props = {
* title: 'Nav Title!',
* ...
* };
*
* const children = navItems.map(item => createWidgetInstance(TabNavItem, {}));
*
* renderWidgetWithChildren(TabNav, props, children, 'navbar');
*
* // or
*
* renderWidgetWithChildren(TabNav, props, children, document.getElementsByClassName('navbar')[0]);
* ```
*/
export const renderWidgetWithChildren = (widget, props, children, container, isSSR = false) => {
renderWidgetInstance(createWidgetInstance(widget, props, children), container, isSSR);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment