UNPKG

rn-dynamic-ui-render

Version:
155 lines (135 loc) 4.34 kB
import React, { createContext, useContext } from "react"; import { useTranslation } from "react-i18next"; import PropTypes from "prop-types"; import { ErrorBoundary } from "./utils"; import { handleColor, handleDynamicData, handleStyles, handleVisible, handleInteractions, evaluateCondition, } from "./utils/handlers"; // Create a Context for components and theme const UIContext = createContext({ components: {}, theme: {}, }); /** * Hook to get the components and theme from the context. This hook will * throw an error if the components and theme are not provided. */ const useUIContext = () => { const context = useContext(UIContext); if (!context.components || !context.theme) { throw new Error( "RNDynamicUIRender must be provided with components and theme" ); } return context; }; /** * Renders a React component based on the provided configuration object. */ const renderComponent = (e, index, handlers, t) => { const { components, theme } = useUIContext(); const Component = components[e.name]; if (!Component) { console.warn(`Component ${e.name} not found`); return null; } const props = e.props || {}; const componentProps = { ...props }; handleStyles(props, componentProps, theme, handlers); handleDynamicData(props, componentProps, handlers); handleColor(props, theme, componentProps); handleInteractions(props, handlers, componentProps); switch (e.name) { case "FlatList": componentProps.keyExtractor = (item, index) => item.id?.toString() || index.toString(); componentProps.ListFooterComponent = () => ( <RNDynamicUIRender data={e.listFooterComponent} handlers={handlers} /> ); componentProps.ListEmptyComponent = () => ( <RNDynamicUIRender data={e.listEmptyComponent} handlers={handlers} /> ); componentProps.ListHeaderComponent = () => ( <RNDynamicUIRender data={e.listHeaderComponent} handlers={handlers} /> ); componentProps.renderItem = ({ item, index }) => ( <RNDynamicUIRender data={e.content} handlers={{ ...handlers, item, index }} /> ); break; default: break; } const content = Array.isArray(e?.content) ? ( <RNDynamicUIRender data={e.content} handlers={handlers} /> ) : ( t(componentProps.textContent || "") || null ); const isVisible = handleVisible(props, handlers) && evaluateCondition(props?.if || true, handlers, handlers?.item); if (isVisible) { return ( <Component {...componentProps} key={index}> {content} </Component> ); } return null; }; const ComponentRenderer = React.memo(({ data, handlers, t }) => { if (!Array.isArray(data)) return null; return data.map((e, i) => renderComponent(e, i, handlers, t)); }); /** * RNDynamicUIRender is a component that combines the UIContext provider * and recursive component rendering. It accepts `components`, `theme`, and * optional translation and renders the UI based on the passed schema (`data`). */ const RNDynamicUIRender = ({ data, handlers, theme, components, translate = false, }) => { const contextValue = useContext(UIContext); const effectiveComponents = components ?? contextValue.components; const effectiveTheme = theme ?? contextValue.theme; if (!effectiveComponents || !effectiveTheme) { throw new Error( "RNDynamicUIRender must be provided with components and theme" ); } const { t } = translate ? useTranslation() : { t: (str) => str }; const content = ( <ErrorBoundary> <ComponentRenderer data={data} handlers={handlers} t={t} /> </ErrorBoundary> ); // Only wrap with a new provider if new values were passed in return components || theme ? ( <UIContext.Provider value={{ components: effectiveComponents, theme: effectiveTheme }} > {content} </UIContext.Provider> ) : ( content ); }; RNDynamicUIRender.propTypes = { data: PropTypes.array, handlers: PropTypes.object, theme: PropTypes.object, components: PropTypes.object, translate: PropTypes.bool, }; export default RNDynamicUIRender;