import React, { ReactElement, useCallback, useContext, useEffect, useMemo, useState } from "react"; import BlocBase from "../BlocBase"; import { BlocClass, BlocHookData, ChangeEvent, ValueType } from "../types"; import { BlacConsumer, ConsumerOptions } from "../BlocConsumer"; import createId from "../createId"; export interface BlocHookOptions> { /* Boolean value if the hook should update the returned state value when the BLoC state changes. Set it to false if you only need access to the methods of a Cubit, or the `add` method on a Bloc */ subscribe?: boolean; /* Decide weather the returned state value should be updated or not. Will have no effect if `subscribe` is false. Receives a ChangeEvent as a parameter and expects a boolean return value. */ shouldUpdate?: (event: ChangeEvent>) => boolean; /* Create a new instance of the bloc, this bloc is not added to the global or any local state. */ create?: () => T; } const defaultBlocHookOptions: BlocHookOptions = { subscribe: true }; class BlocRuntimeError { error: Error; constructor(message?: string) { this.error = new Error(message); } } class NoValue { } export class BlacReact extends BlacConsumer { private readonly _blocsGlobal: BlocBase[]; private _contextLocalProviderKey = React.createContext('none'); constructor(blocs: BlocBase[], options?: ConsumerOptions) { super(blocs, options); this._blocsGlobal = blocs; this.BlocProvider = this.BlocProvider.bind(this); this.BlocBuilder = this.BlocBuilder.bind(this); } useBloc = >( blocClass: BlocClass, options: BlocHookOptions = {} ): BlocHookData => { const mergedOptions: BlocHookOptions = { ...defaultBlocHookOptions, ...options }; let blocInstance: BlocBase | undefined = useMemo(() => options.create ? options.create() : undefined, []); if (!blocInstance) { const localProviderKey = useContext(this._contextLocalProviderKey); const localBlocInstance = useMemo(() => this.getLocalBlocForProvider(localProviderKey, blocClass), []); blocInstance = useMemo(() => localBlocInstance || this.getGlobalBlocInstance(this._blocsGlobal, blocClass), []); } const { subscribe, shouldUpdate = true } = mergedOptions; if (!blocInstance) { const name = blocClass.prototype.constructor.name; const error = new BlocRuntimeError(`"${name}" no bloc with this name was found in the global context. # Solutions: 1. Wrap your code in a BlocProvider. 2. Add "${name}" to the "Blac" constructor: const state = new Blac( [ ... new ${name}(), ] ) `); console.error(error.error); return ([ NoValue, {}, { error, complete: true } ] as unknown) as BlocHookData; } const [data, setData] = useState>(blocInstance.state as ValueType); const updateData = useCallback((nextState: ValueType) => { if (shouldUpdate === true || shouldUpdate({ nextState, currentState: data })) { setData(nextState); } }, []); useEffect(() => { if (subscribe) { const subscription = blocInstance?.subscribe({ next: updateData }); return () => { subscription?.unsubscribe(); }; } }, []); return [ data, blocInstance as T ]; }; // Components BlocBuilder>(props: { blocClass: BlocClass; builder: (data: BlocHookData) => ReactElement; shouldUpdate?: (event: ChangeEvent>) => boolean; }): ReactElement | null { const hook = this.useBloc(props.blocClass, { shouldUpdate: props.shouldUpdate }); return props.builder(hook); }; BlocProvider>(props: { children?: ReactElement | ReactElement[] | false; bloc: T | ((id: string) => T); }): ReactElement { const id = useMemo(() => createId(), []); const localProviderKey = useContext(this._contextLocalProviderKey); const bloc = useMemo(() => { const newBloc = typeof props.bloc === "function" ? props.bloc(id) : props.bloc; if (newBloc) { this.addLocalBloc({ bloc: newBloc, id, parent: localProviderKey }); } else { console.error(`BLoC is undefined`); } return newBloc; }, []); const context = useMemo>>(() => { return React.createContext>(bloc); }, [bloc]); useEffect(() => { return () => { this.removeLocalBloc(id, bloc); }; }, []); return ( {props.children} ); }; withBlocProvider =

(bloc: BlocBase | (() => BlocBase)) => (Component: React.ComponentType

): React.ComponentType

=> { const {BlocProvider} = this; return class WithBlocProvider extends React.Component

{ render(): React.ComponentType

{ return ( ) as unknown as React.ComponentType

; } }; } }