Patrón BLoC con React Hooks
El Patrón BLoC ha sido diseñado por Paolo Soares y Cong Hui, de Google y presentado por primera vez durante la DartConf 2018 (23-24 de enero de 2018). Ver el vídeo en YouTube.
BLoC significa Business Logic Component (Componente de Lógica de Negocio). Inicialmente concebido para compartir código entre Flutter y Angular Dart, funciona independientemente de la plataforma: aplicación web, aplicación móvil o back-end.
Ofrece una alternativa al puerto de Redux para flutter usando streams de Dart. Usaremos Observables de RxJS, aunque xstream funciona igualmente bien.
En resumen, el BLoC:
- contendrá lógica de negocio (idealmente en aplicaciones más grandes tendremos múltiples BLoCs)
- dependerá exclusivamente del uso de Observables tanto para entrada (Observer) como para salida (Observable)
- permanecerá independiente de la plataforma
- permanecerá independiente del entorno
¿Cómo funciona BLoC?
Otros han explicado BLoC mejor que yo, así que cubriré solo lo básico.

El BLoC mantiene la lógica de negocio; los componentes desconocen sus internos. Los componentes envían eventos al BLoC vía Observers y reciben notificaciones vía Observables.
Implementando el BLoC
Aquí está un BLoC de búsqueda básico en TypeScript usando RxJS:
export class SearchBloc {private _results$: Observable<string[]>;private _preamble$: Observable<string>;private _query$ = new BehaviorSubject<string>("");constructor(private api: API) {this._results$ = this._query$.pipe(switchMap((query) => {return observableFrom(this.api.search(query));}),);this._preamble$ = this.results$.pipe(withLatestFrom(this._query$, (_, q) => {return q ? `Resultados para ${q}` : "Todos los resultados";}),);}get results$(): Observable<string[]> {return this._results$;}get preamble$(): Observable<string> {return this._preamble$;}get query(): Observer<string> {return this._query$;}dispose() {this._query$.complete();}}
results$ y preamble$ exponen valores asíncronos que cambian cuando query
cambia.
query expone un Observer<string> para que los componentes añadan nuevos
valores. Dentro de SearchBloc, _query$: BehaviorSubject<string> sirve como
fuente del stream, y el constructor declara _results$ y _preamble$ para
responder a _query$.
Usándolo en React
Para usarlo en React, crea una instancia del BLoC y compártela con los componentes hijos vía contexto de React.
const searchBloc = new SearchBloc(new API());const SearchContext = React.createContext(searchBloc);
Expónlo usando el proveedor de contexto:
const App = () => {const searchBloc = useContext(SearchContext);useEffect(() => {return searchBloc.dispose;}, [searchBloc]);return (<SearchContext.Provider><SearchInput /><ResultList /></SearchContext.Provider>);};
El useEffect devuelve el método dispose, completando el observer cuando el
componente se desmonta.
Publica cambios al BLoC desde el componente SearchInput:
const SearchInput = () => {const searchBloc = useContext(SearchContext);const [query, setQuery] = useState("");useEffect(() => {searchBloc.query.next(query);}, [searchBloc, query]);return (<inputtype="text"name="Search"value={query}onChange={({ target }) => setQuery(target.value)}/>);};
Obtenemos el BLoC vía useContext, luego useEffect publica cada cambio de
consulta al BLoC.
Ahora el ResultList:
const ResultList = () => {const searchBloc = useContext(SearchContext);const [results, setResults] = useState([]);useEffect(() => {return searchBloc.results$.subscribe(setResults);}, [searchBloc]);return (<div>{results.map(({ id, name }) => (<div key={id}>{name}</div>))}</div>);};
Usamos useContext para obtener el BLoC, luego useEffect se suscribe a los
cambios de results$ para actualizar el estado local. Devolver la suscripción
cancela la suscripción cuando el componente se desmonta.
Pensamientos finales
El código final es directo con conocimiento básico de Observables y hooks.
El código es legible y mantiene la lógica de negocio fuera de los componentes.
Debemos recordar desuscribirnos de los observables y desechar el BLoC al
desmontar, pero hooks personalizados como useBlocObservable y
useBlocObserver podrían resolver esto. Planeo probarlo en un proyecto paralelo
donde uso este patrón.