Я пытался реализовать глобальный экран загрузки при получении данных из 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.
Удивительно, спасибо, у меня есть еще один вопрос, я переместил реализацию из Layout.js в app.js, и она перестала работать, у вас есть идеи, что может быть причиной?
Вы пытаетесь прочитать состояние loading до его установки. Вам нужно вызвать useLoading в любом месте компонента <LoadingProvider>. Вот почему у вас есть if (!context) внутри определения функции useLoading.
Причина в том, что ваш 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?
Кстати, я не могу найти ReactDOM.render в своем проекте. Это как-то связано с тем, что я использую Next.js?
Pageprops необходимо передать от app.js к Child.
О, только что увидел, что вы внесли некоторые изменения в ответ. Пробую сейчас. Спасибо!
Next.js
, вероятно, делает ReactDOM.render
под капотом, вы, вероятно, не сможете его увидеть. Идея состоит в том, чтобы делегировать LoadingProvider
родителю app.js
, чтобы вы могли получить доступ к состоянию внутри. Однако, если app.js
— это отдельная страница, так как вы используете Next.js, вы можете использовать второй подход к созданию дочернего компонента.
Хорошо, я внес некоторые изменения в соответствии с вашими изменениями и реализовал второе решение, которое вы предоставили, но я вижу только белый экран.
Я сделал небольшую ошибку во втором варианте, когда не добавил ключевое слово return. Я также добавил альтернативное решение, оба должны работать, попробуйте!
Проблема в том, что у меня было раньше, он добавляет LoadingScreen перед __next div, а не поверх него.
Хорошо, это потому, что вы рендерите другие элементы div позже. Вы можете использовать вариант 2 и использовать тернарный оператор для отображения экрана загрузки только в том случае, если он загружается. Или, если вы хотите использовать вариант 3, вы можете сделать экран загрузки поверх основного экрана, используя position: absolute; в CSS. Я бы рекомендовал использовать первый.
Я сделал это, и это отображается, но отображается в середине основного контента, а не поверх SideBar и AppBar. (Я использовал третий вариант решения, который вы предоставили)
Я думаю, вам нужно поиграть с css для этого. Я считаю, что логика контекста работает сейчас!
Вы назвали значение loading, а не isLoading. Исправьте это в Layout.js