import React , { createContext , FC , useContext , useEffect , useMemo } from "react" ; import debounce from "lodash.debounce" ; import { useAdmin } from "./admin" ; import { ComponentType } from "react" ; const useComponent = Component => { const { wrappers } = useAdmin ( ) ; const recipe = wrappers . get ( Component ) ; if ( ! recipe ) { return Component ; } return recipe . component ; } ; const ComposableContext = createContext ( [ ] ) ; ComposableContext . displayName = "ComposableContext" ; function useComposableParents ( ) { const context = useContext ( ComposableContext ) ; if ( ! context ) { return [ ] ; } return context ; } const createEmptyRenderer = ( name ) => { return function EmptyRenderer ( ) { useEffect ( ( ) => { const debounced = debounce ( ( ) => { console . info ( `<${ name }/> is not implemented! To provide an implementation, use the <Compose/> component.` ) ; } , 100 ) ; return ( ) => { debounced . cancel ( ) ; } ; } , [ ] ) ; return null ; } ; } ; type ComposableFC < TProps > = FC < TProps > & { original : ComponentType < TProps > ; originalName : string ; } ; export function makeComposable < TProps > ( name , Component ? ) { if ( ! Component ) { Component = createEmptyRenderer ( name ) ; } const Composable = props => { const parents = useComposableParents ( ) ; const WrappedComponent = useComponent ( Component ) ; const context = useMemo ( ( ) => [ ... parents , name ] , [ parents , name ] ) ; return ( < ComposableContext . Provider value = { context } > < WrappedComponent { ... props } > { props . children } < /WrappedComponent> < /ComposableContext.Provider>); } ; Component . displayName = name ; Composable . original = Component ; Composable . originalName = name ; Composable . displayName = `Composable<${ name }>` ; return Composable ; }