diff --git a/README.md b/README.md index 2fa1c41..65ac829 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,28 @@ export default function RootLayout({ children }) { } ``` +### Programmatic Control (Client Components) + +Have an async operation before an eventual route change? You might be interested in holy-loader's manual controls. These only work in client components! + +```typescript +'use client'; + +import { startHolyLoader, stopHolyLoader } from 'holy-loader'; + +try { + startHolyLoader(); // Trigger the loader beforehand + await signOut(); // Example async operation +} catch (error) { + stopHolyLoader(); // Stop the loader on error + // Handle the error +} finally { + stopHolyLoader(); // Stop the loader after the operation, and potentially do something else + /* OR */ + router.push('/'); // Navigate to the desired route, which will automatically stop the loader +} +``` + ## Common issues Prevent triggering the loader when clicking a Button within a Next link: diff --git a/src/HolyProgress.ts b/src/HolyProgress.ts index 0663e19..c546dda 100644 --- a/src/HolyProgress.ts +++ b/src/HolyProgress.ts @@ -184,15 +184,15 @@ export class HolyProgress { * @public * @returns {HolyProgress} The current instance for chaining methods. */ - public start = (): HolyProgress => { + public readonly start = (): HolyProgress => { if (this.status === null) { this.setTo(0); - } - this.startTrickle(); + this.startTrickle(); - if (this.settings.showSpinner === true) { - this.createSpinner(); + if (this.settings.showSpinner === true) { + this.createSpinner(); + } } return this; @@ -200,7 +200,7 @@ export class HolyProgress { /** * Performs automatic incrementation of the progress bar. - * This function is recursive and continues to increment the progress at intervals defined by `sppeed`. + * This function is recursive and continues to increment the progress at intervals defined by `speed`. * @private */ private readonly startTrickle = (): void => { @@ -219,7 +219,7 @@ export class HolyProgress { * @public * @returns {HolyProgress} The current instance for chaining methods. */ - public complete = (): HolyProgress => this.setTo(1); + public readonly complete = (): HolyProgress => this.setTo(1); /** * Calculates an increment value based on the current status of the progress. diff --git a/src/constants.ts b/src/constants.ts index b276df8..75fee07 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,3 +8,6 @@ export const DEFAULTS = { showSpinner: false, boxShadow: undefined, }; + +export const START_HOLY_EVENT = 'holy-progress-start'; +export const STOP_HOLY_EVENT = 'holy-progress-stop'; diff --git a/src/index.tsx b/src/index.tsx index c11ace3..90c21f3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { HolyProgress } from './HolyProgress'; -import { DEFAULTS } from './constants'; +import { DEFAULTS, START_HOLY_EVENT, STOP_HOLY_EVENT } from './constants'; export interface HolyLoaderProps { /** @@ -97,6 +97,20 @@ export const isSameHost = (currentUrl: string, newUrl: string): boolean => { ); }; +/** + * Dispatches the event to manually start the HolyLoader progress bar. + */ +export const startHolyLoader = (): void => { + document.dispatchEvent(new Event(START_HOLY_EVENT)); +}; + +/** + * Dispatches the event to manually stop the HolyLoader progress bar. + */ +export const stopHolyLoader = (): void => { + document.dispatchEvent(new Event(STOP_HOLY_EVENT)); +}; + /** * HolyLoader is a React component that provides a customizable top-loading progress bar. * @@ -113,18 +127,26 @@ const HolyLoader = ({ boxShadow = DEFAULTS.boxShadow, showSpinner = DEFAULTS.showSpinner, }: HolyLoaderProps): null => { - React.useEffect(() => { - let holyProgress: HolyProgress; + const holyProgressRef = React.useRef(null); + React.useEffect(() => { const startProgress = (): void => { + if (holyProgressRef.current === null) { + return; + } + try { - holyProgress.start(); + holyProgressRef.current.start(); } catch (error) {} }; const stopProgress = (): void => { + if (holyProgressRef.current === null) { + return; + } + try { - holyProgress.complete(); + holyProgressRef.current.complete(); } catch (error) {} }; @@ -192,25 +214,31 @@ const HolyLoader = ({ }; try { - holyProgress = new HolyProgress({ - color, - height, - initialPosition, - easing, - speed, - zIndex, - boxShadow, - showSpinner, - }); + if (holyProgressRef.current === null) { + holyProgressRef.current = new HolyProgress({ + color, + height, + initialPosition, + easing, + speed, + zIndex, + boxShadow, + showSpinner, + }); + } document.addEventListener('click', handleClick); + document.addEventListener(START_HOLY_EVENT, startProgress); + document.addEventListener(STOP_HOLY_EVENT, stopProgress); stopProgressOnHistoryUpdate(); } catch (error) {} return () => { document.removeEventListener('click', handleClick); + document.removeEventListener(START_HOLY_EVENT, startProgress); + document.removeEventListener(STOP_HOLY_EVENT, stopProgress); }; - }, []); + }, [holyProgressRef]); return null; };