Global screen loader in react

Viewed 7191

I am looking for a solution for using a global screen loader in react.

I am not that much familiar to react context, but I was wondering if that could help me here. Basically I am introducing a screenloader and I was thinking that maybe the best way would be to have a global loader somewhere in main component.So to conclude:

  • I want to have global loader in main component
  • I want to update the state of global loader wherever I want in app
  • I don't want to pollute all the components with ScreenLoaders where I need to use it
  • I want to use hooks for it

So is there a way to have a global state of loader/loaderText and setting and resetting whenever needed using context?

If there is a simple way to do it, then do you think there might be any drawbacks of using such solution? Maybe that's an overkill for it.

3 Answers

What about creating a custom hook, useLoading, which can be used in any component that gives access to loading and setLoading from global context?

// LoadingContext.js
import { createContext, useContext, useState } from "react";

const LoadingContext = createContext({
  loading: false,
  setLoading: null,
});

export function LoadingProvider({ children }) {
  const [loading, setLoading] = useState(false);
  const value = { loading, setLoading };
  return (
    <LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
  );
}

export function useLoading() {
  const context = useContext(LoadingContext);
  if (!context) {
    throw new Error("useLoading must be used within LoadingProvider");
  }
  return context;
}
// App.jsx
import { LoadingProvider } from "./LoadingContext";

function App() {
    return (
        <LoadingProvider>
            <RestOfYourApp />
       </LoadingProvider>
    );
}
// RestOfYourApp.js
import { useLoading } from "./LoadingContext";

function RestOfYourApp() {
    const { loading, setLoading } = useLoading();
    return (
      {loading && <LoadingComponent />}
      ...
    );
}

useLoader.js (hook)

import React, { useState } from "react";
import Loader from "./loader";
const useLoader = () => {
  const [loading, setLoading] = useState(false);
  return [
    loading ? <Loader /> : null,
    () => setLoading(true),
    () => setLoading(false),
  ];
};
export default useLoader;

loader.js (loader componenet)

import React from "react";
import styled from "styled-components";
import spinner from "./loader.gif"; // create gif from https://loading.io
import Color from "../../Constant/Color";

const Loader = () => {
  return (
    <LoaderContainer>
      <LoaderImg src={spinner} />
    </LoaderContainer>
  );
};


const LoaderContainer = styled.div`
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  background: ${Color.greyBg};
  z-index: 100;
`;
const LoaderImg = styled.img`
  position: absolute;
`;

export default Loader;

Using Loader hook

import useLoader from "../../../hooks/loader/useLoader"; /// import loader hook

const App = (props) => {
const [loader, showLoader, hideLoader] = useLoader(); //initialize useLoader hook


useEffect(() => {
    showLoader(); /// loading starts
   
    Axios.post("url")
      .then((res) => {
        hideLoader(); // loading stops
      })
      .catch((error) => {
        hideLoader();// loading stops
      });
  }, []);

return (
<>
{loader} /// important 

//// add your elements /////
</>
)
}
export default App;

Some more easy way Create Provider with context and hook within single file

import React, {useRef} from 'react';
import {Loader} from '@components';

const LoaderContext = React.createContext();

export function LoaderProvider({children}) {
  const ref = useRef();
  const startLoader = () => ref.current.start();
  const stopLoader = () => ref.current.stop();
  const value = React.useMemo(
    () => ({ref, startLoader, stopLoader}),
    [ref, startLoader, stopLoader]
  );

  return (
    <LoaderContext.Provider value={value}>
      {children}
      <Loader ref={ref} />
    </LoaderContext.Provider>
  );
}

export const useLoader = () => React.useContext(LoaderContext);

in App.js add provider

import {StoreProvider} from 'easy-peasy';
import React from 'react';
import {StatusBar, View} from 'react-native';
import colors from './src/assets/colors';
import Navigation from './src/navigation/routes';
import {LoaderProvider} from './src/providers/LoaderProvider';
import {ToastProvider} from './src/providers/ToastProvider';
import store from './src/redux/store';
import globalStyles from './src/styles/index';

import('./src/helpers/ReactotronConfig');

function App() {
  return (
    <StoreProvider store={store}>
      <StatusBar
        barStyle="light-content"
        backgroundColor={colors.backgroundDark}
        translucent={false}
      />
      <ToastProvider>
        <LoaderProvider>
          <View style={globalStyles.flex}>
            <Navigation />
          </View>
        </LoaderProvider>
      </ToastProvider>
    </StoreProvider>
  );
}

export default App;

And in any screen use like this way

import {useLoader} from '../../providers/LoaderProvider';
const {startLoader, stopLoader} = useLoader();

Loader.js

import React, {forwardRef, useImperativeHandle, useState} from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {wp} from '../../styles/responsive';

function Loader(props, ref) {
  const [loading, setLoading] = useState(0);

  useImperativeHandle(
    ref,
    () => ({
      start: () => {
        const loadingCount = loading + 1;
        setLoading(loadingCount);
      },
      stop: () => {
        const loadingCount = loading > 0 ? loading - 1 : 0;
        setLoading(loadingCount);
      },
      isLoading: () => loading >= 1,
    }),
    [],
  );

  if (!loading) {
    return null;
  }

  return (
    <View style={styles.container}>
      <ActivityIndicator size={'small'} color={'#f0f'} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFill,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#11111150',
    zIndex: 999,
    elevation: 999,
  },
});

export default forwardRef(Loader);
Related