Показывать экран загрузки глобально в React.js при вызове его с разных страниц

Я пытался реализовать глобальный экран загрузки при получении данных из API, я прочитал этот ответ и попытался реализовать что-то подобное.

Загрузка.provider.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.js

На этот экран добавлен экран загрузки:

import Layout from '@/components/Layout/Layout';
import { LoadingProvider, useLoading } from '@/Providers/loading.provider';
import LoadingScreen from "./LoadingScreen";

export default function App({ Component, pageProps }) {
  const { loading } = useLoading();

  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <AppStateProvider>
          { loading && <LoadingScreen loading = {true} bgColor='#fff' spinnerColor = {'#00A1FF'} textColor='#676767'></LoadingScreen> }
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </AppStateProvider>
      </LoadingProvider>
    </>
  );
};

У меня есть компонент макета, который содержит компоненты SideMenu, top AppBar и main:

Layout.js

export default function Layout({ children }) {
  return (
    <Fragment>
      <Box sx = {{ display: 'flex' }}>
        <AppBar />
        <SideMenu />
        <Box component = "main" sx = {mainContentWrapper}>
          <div className = {styles.container}>
            <main className = {styles.main}>
              {children}
            </main>
          </div>
        </Box>
      </Box>
    </Fragment>
  );
}

Теперь я пытался показать это со страницы ([identifier].js):

import { useLoading } from "@/Providers/loading.provider";

export default function MainPage({ response }) {
  const { loading, setLoading } = useLoading();
  const btnClickedAll = async (data) => {
    setLoading(true);
  };

  return (
    <Fragment>
      <Button sx = {{ ml: 2 }} key = {'btnAll'} onClick = {btnClickedAll} variant = "contained">All</Button>,
    </Fragment>
  );
}

Loading вызывается после установки его с помощью setLoading(true), но состояние внутри app.js не меняется. Может ли кто-нибудь помочь мне и указать мне правильное направление?


Отредактировано: внесены некоторые изменения в код в соответствии с предложением @DustInComp.

Вы назвали значение loading, а не isLoading. Исправьте это в Layout.js

DustInComp 13.02.2023 11:15

Удивительно, спасибо, у меня есть еще один вопрос, я переместил реализацию из Layout.js в app.js, и она перестала работать, у вас есть идеи, что может быть причиной?

ytpm 13.02.2023 12:01

Вы пытаетесь прочитать состояние loading до его установки. Вам нужно вызвать useLoading в любом месте компонента <LoadingProvider>. Вот почему у вас есть if (!context) внутри определения функции useLoading.

GoodMan 13.02.2023 14:10
Слишком много useState? Давайте useReducer!
Слишком много useState? Давайте useReducer!
Современный фронтенд похож на старую добрую веб-разработку, но с одной загвоздкой: страница в браузере так же сложна, как и бэкенд.
Создание микрофронтендов с Angular и федерацией модулей в монорепо: Пошаговое руководство
Создание микрофронтендов с Angular и федерацией модулей в монорепо: Пошаговое руководство
Микрофронтенды стали популярным архитектурным паттерном для веб-разработки. С появлением федерации модулей в Webpack 5 реализация микрофронтендов...
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
Если вы уже умеете работать с React, создание мобильных приложений для iOS и Android - это новое приключение, в котором вы сможете применить свои...
Революционная веб-разработка ServiceNow
Революционная веб-разработка ServiceNow
В быстро развивающемся мире веб-разработки ServiceNow для достижения успеха крайне важно оставаться на вершине последних тенденций и технологий. По...
Как добавить SEO(Search Engine Optimization) в наше веб-приложение и как это работает?
Как добавить SEO(Search Engine Optimization) в наше веб-приложение и как это работает?
Заголовок веб-страницы играет наиболее важную роль в SEO, он помогает поисковой системе понять, о чем ваш сайт.
Руководство для начинающих по веб-разработке на React.js
Руководство для начинающих по веб-разработке на React.js
Веб-разработка - это захватывающая и постоянно меняющаяся область, которая постоянно развивается благодаря новым технологиям и тенденциям. Одним из...
1
3
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Причина в том, что ваш LoadingProvider находится внутри вашего app.js, и только дети внутри LoadingProvider будут иметь доступ к состоянию загрузки.

Опция 1

Что вы можете сделать, так это переместить весь app.js в LoadingProvider в родителя, возможно, index.js.

В index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import './index.css';
import './bootstrap.min.css';
import App from './App';

ReactDOM.render(
    <LoadingProvider>
        <App />
    </LoadingProvider>,
    document.getElementById('root')
);

ИЛИ

Вариант 2

Вы можете создать дочерний компонент всего внутри app.js.

В Child.js (назовите его как хотите)

const Child = ({ Component, pageProps }) => {
    const { loading } = useLoading();
     <AppStateProvider>
          { loading && <LoadingScreen loading = {true} bgColor='#fff' spinnerColor = {'#00A1FF'} textColor='#676767'></LoadingScreen> }
          <Layout>
            <Component {...pageProps} />
          </Layout>
     </AppStateProvider>
};

export default Child;

И в app.js

export default function App({ Component, pageProps }) {
  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <Child Component = {Component} pageProps = {pageProps}/>
      </LoadingProvider>
    </>
  );
};

ИЛИ

Вариант 3

Вы можете создать компонент загрузчика загрузчика и импортировать его в app.js.

В Loader.js (назовите его как вам угодно)

const Loader = () => {
    const { loading } = useLoading();
    return loading && <LoadingScreen loading = {true} bgColor='#fff' spinnerColor = {'#00A1FF'} textColor='#676767'></LoadingScreen>;        
};

export default Loader;

И в app.js

export default function App({ Component, pageProps }) {
  return (
    <>
      <CssBaseline />
      <LoadingProvider>
        <AppStateProvider>
          <Loader />
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </AppStateProvider>
      </LoadingProvider>
    </>
  );
};

Спасибо за ваш ответ, во втором варианте, который вы предоставили, откуда берется ...PageProps?

ytpm 13.02.2023 14:53

Кстати, я не могу найти ReactDOM.render в своем проекте. Это как-то связано с тем, что я использую Next.js?

ytpm 13.02.2023 14:55

Pageprops необходимо передать от app.js к Child.

G Sriram 13.02.2023 14:56

О, только что увидел, что вы внесли некоторые изменения в ответ. Пробую сейчас. Спасибо!

ytpm 13.02.2023 14:58
Next.js, вероятно, делает ReactDOM.render под капотом, вы, вероятно, не сможете его увидеть. Идея состоит в том, чтобы делегировать LoadingProvider родителю app.js, чтобы вы могли получить доступ к состоянию внутри. Однако, если app.js — это отдельная страница, так как вы используете Next.js, вы можете использовать второй подход к созданию дочернего компонента.
G Sriram 13.02.2023 14:58

Хорошо, я внес некоторые изменения в соответствии с вашими изменениями и реализовал второе решение, которое вы предоставили, но я вижу только белый экран.

ytpm 13.02.2023 15:02

Я сделал небольшую ошибку во втором варианте, когда не добавил ключевое слово return. Я также добавил альтернативное решение, оба должны работать, попробуйте!

G Sriram 13.02.2023 15:08

Проблема в том, что у меня было раньше, он добавляет LoadingScreen перед __next div, а не поверх него.

ytpm 13.02.2023 15:27

Хорошо, это потому, что вы рендерите другие элементы div позже. Вы можете использовать вариант 2 и использовать тернарный оператор для отображения экрана загрузки только в том случае, если он загружается. Или, если вы хотите использовать вариант 3, вы можете сделать экран загрузки поверх основного экрана, используя position: absolute; в CSS. Я бы рекомендовал использовать первый.

G Sriram 13.02.2023 15:29

Я сделал это, и это отображается, но отображается в середине основного контента, а не поверх SideBar и AppBar. (Я использовал третий вариант решения, который вы предоставили)

ytpm 13.02.2023 15:35

Я думаю, вам нужно поиграть с css для этого. Я считаю, что логика контекста работает сейчас!

G Sriram 15.02.2023 06:09

Другие вопросы по теме