@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
text/typescript
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,
]
);
};