UNPKG

@applicaster/zapp-react-native-ui-components

Version:

Applicaster Zapp React Native ui components for the Quick Brick App

117 lines (96 loc) 3.26 kB
import * as React from "react"; import { isNil, set, lensIndex, slice } from "ramda"; import { BehaviorSubject } from "rxjs"; import { useRefWithInitialValue } from "@applicaster/zapp-react-native-utils/reactHooks/state/useRefWithInitialValue"; const reducer = (state, { payload }) => { if (!isNil(payload) && !state[payload]) { return set(lensIndex(payload), true)(state); } return state; }; type LoadingState = { index: number; done: boolean; waitForAllComponents: boolean; }; type Return = { loadingState: BehaviorSubject<LoadingState>; onLoadFinished: (index: number) => void; onLoadFailed: ({ error, index }: { error: Error; index: number }) => void; shouldShowLoadingError: boolean; arePreviousComponentsLoaded: (index: number) => boolean; }; // TODO: Take this value from Zapp configuration, when feature is added to GeneralScreen const SHOULD_FAIL_ON_COMPONENT_LOADING = false; const createLoadingStateObservable = (count: number) => new BehaviorSubject<LoadingState>({ index: -1, done: count === 0, waitForAllComponents: SHOULD_FAIL_ON_COMPONENT_LOADING, }); export const useLoadingState = ( count: number, onLoadDone: () => void ): Return => { const componentStateRef = React.useRef(new Array(count).fill(false)); const [loadingError, setLoadingError] = React.useState(null); const loadingState = useRefWithInitialValue<BehaviorSubject<LoadingState>>( () => createLoadingStateObservable(count) ); const arePreviousComponentsLoaded = React.useCallback((index) => { if (index === 0) { return true; } const componentsBefore = slice(0, index, componentStateRef.current); return componentsBefore.every(Boolean); }, []); const dispatch = React.useCallback(({ payload }) => { const newState = reducer(componentStateRef.current, { payload }); componentStateRef.current = newState; const isDone = arePreviousComponentsLoaded(count - 1); const state = loadingState.current.getValue(); const newLoadingState = { ...state, index: state.index < payload ? payload : state.index, done: isDone, }; loadingState.current.next(newLoadingState); if (isDone) { onLoadDone(); } }, []); const handleComponentLoaded = React.useCallback( (index) => dispatch({ payload: index }), [] ); const handleComponentLoadErrorWhenNeedToFail = React.useCallback( ({ error }) => { if (error !== loadingError) { setLoadingError(error); } }, [] ); const handleComponentLoadErrorWhenNoNeedToFail = React.useCallback( ({ index }) => handleComponentLoaded(index), [] ); return React.useMemo( () => ({ loadingState: loadingState.current, onLoadFinished: handleComponentLoaded, onLoadFailed: SHOULD_FAIL_ON_COMPONENT_LOADING ? handleComponentLoadErrorWhenNeedToFail : handleComponentLoadErrorWhenNoNeedToFail, shouldShowLoadingError: SHOULD_FAIL_ON_COMPONENT_LOADING && loadingError, arePreviousComponentsLoaded, }), [ loadingError, handleComponentLoaded, handleComponentLoadErrorWhenNeedToFail, handleComponentLoadErrorWhenNoNeedToFail, arePreviousComponentsLoaded, ] ); };