import React, { createContext, FunctionComponent, ReactNode, useContext, useMemo } from 'react' import { isFunction, isShallowEqual } from '@spicy-hooks/utils' import { useDistinctValue } from './use-distinct-value' /** * A `Provider` like component through which you can override selected dependencies. * * @see [[createUseDependencies]] */ export type OverrideComponent> = FunctionComponent<{ values: Partial, children?: ReactNode }> /** * A hook that fetches dependencies from a dependency injection layer. * In addition to that it carries a `Override` component that can be used to override selected dependencies. * * @typeParam T shape of dependency object * @see [[createUseDependencies]] */ export type UseDependencies> = (() => T) & { /** * When mounted, this component overrides selected dependencies for usages of the hook within any child component. */ Override: OverrideComponent } interface ValuesHolder { values?: T factory?: () => T } function materializeValues (holder: ValuesHolder): T { if (holder.factory) { holder.values = holder.factory() delete holder.factory } return holder.values! } /** * This function sets up a very simple context based dependency injection layer. * It creates a `useDependencies` hook that can be used to fetch the dependencies from any component. * * **Example:** * ```jsx * const useMyDependencies = createUseContext({foo: 'X', bar: 1}) * * const MyComponent = () => { * const {foo} = useMyDependencies() * ... * } * * const MyApp = () => ( * * ... * > (defaultsOrFactory: T | (() => T)): UseDependencies { const holder: ValuesHolder = isFunction(defaultsOrFactory) ? { factory: defaultsOrFactory } : { values: defaultsOrFactory } const context = createContext(holder) const useDependencies = (() => { const holder = useContext(context) return materializeValues(holder) }) as UseDependencies // noinspection UnnecessaryLocalVariableJS const Override: OverrideComponent = ({ values, children }) => { const parentHolder = useContext(context) const parentValues = materializeValues(parentHolder) const mergedValues = { ...parentValues, ...values } const distinctValues = useDistinctValue(mergedValues, isShallowEqual) const newHolder = useMemo(() => ({ values: distinctValues }), [distinctValues]) return {children} } useDependencies.Override = Override Override.displayName = 'OverrideDependencies' return useDependencies }