mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-18 09:07:57 +02:00
refactor(theme-common): unify missing context errors (#6826)
* refactor(theme-common): unify missing context errors * update test * more robust
This commit is contained in:
parent
5c60f41e1b
commit
c387a177e8
11 changed files with 35 additions and 28 deletions
|
@ -120,6 +120,7 @@ export {
|
|||
export {
|
||||
useIsomorphicLayoutEffect,
|
||||
useDynamicCallback,
|
||||
ReactContextError,
|
||||
} from './utils/reactUtils';
|
||||
|
||||
export {isRegexpStringMatch} from './utils/regexpUtils';
|
||||
|
|
|
@ -71,7 +71,7 @@ describe('docsUtils', () => {
|
|||
expect(
|
||||
() => renderHook(() => useDocsVersion()).result.current,
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"This hook requires usage of <DocsVersionProvider>"`,
|
||||
`"Hook useDocsVersion is called outside the <DocsVersionProvider>. "`,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -93,7 +93,7 @@ describe('docsUtils', () => {
|
|||
expect(
|
||||
() => renderHook(() => useDocsSidebar()).result.current,
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"This hook requires usage of <DocsSidebarProvider>"`,
|
||||
`"Hook useDocsSidebar is called outside the <DocsSidebarProvider>. "`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import React, {
|
|||
} from 'react';
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||
import {createStorageSlot} from './storageUtils';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
import {useThemeConfig} from './useThemeConfig';
|
||||
|
||||
export const AnnouncementBarDismissStorageKey =
|
||||
|
@ -110,12 +111,10 @@ export function AnnouncementBarProvider({
|
|||
);
|
||||
}
|
||||
|
||||
export const useAnnouncementBar = (): AnnouncementBarAPI => {
|
||||
export function useAnnouncementBar(): AnnouncementBarAPI {
|
||||
const api = useContext(AnnouncementBarContext);
|
||||
if (!api) {
|
||||
throw new Error(
|
||||
'useAnnouncementBar(): AnnouncementBar not found in React context: make sure to use the AnnouncementBarProvider on top of the tree',
|
||||
);
|
||||
throw new ReactContextError('AnnouncementBarProvider');
|
||||
}
|
||||
return api;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {ReactNode} from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useContext,
|
||||
useMemo,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
import {createStorageSlot} from './storageUtils';
|
||||
|
@ -148,8 +149,9 @@ export function useColorMode(): ColorModeContextValue {
|
|||
ColorModeContext,
|
||||
);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
'"useColorMode()" is used outside of "Layout" component. Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.',
|
||||
throw new ReactContextError(
|
||||
'ColorModeProvider',
|
||||
'Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, {type ReactNode, useMemo, useState, useContext} from 'react';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
|
||||
const EmptyContext: unique symbol = Symbol('EmptyContext');
|
||||
const Context = React.createContext<
|
||||
|
@ -33,9 +34,7 @@ export function DocSidebarItemsExpandedStateProvider({
|
|||
export function useDocSidebarItemsExpandedState(): DocSidebarItemsExpandedState {
|
||||
const contextValue = useContext(Context);
|
||||
if (contextValue === EmptyContext) {
|
||||
throw new Error(
|
||||
'This hook requires usage of <DocSidebarItemsExpandedStateProvider>',
|
||||
);
|
||||
throw new ReactContextError('DocSidebarItemsExpandedStateProvider');
|
||||
}
|
||||
return contextValue;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import React, {
|
|||
} from 'react';
|
||||
import {useThemeConfig, type DocsVersionPersistence} from '../useThemeConfig';
|
||||
import {isDocsPluginEnabled} from '../docsUtils';
|
||||
import {ReactContextError} from '../reactUtils';
|
||||
|
||||
import {
|
||||
useAllDocsData,
|
||||
|
@ -159,9 +160,7 @@ function DocsPreferredVersionContextProviderUnsafe({
|
|||
export function useDocsPreferredVersionContext(): DocsPreferredVersionContextValue {
|
||||
const value = useContext(Context);
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
'Can\'t find docs preferred context, maybe you forgot to use the "DocsPreferredVersionContextProvider"?',
|
||||
);
|
||||
throw new ReactContextError('DocsPreferredVersionContextProvider');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import type {
|
|||
PropSidebarBreadcrumbsItem,
|
||||
} from '@docusaurus/plugin-content-docs';
|
||||
import {isSamePath} from './pathUtils';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
import {useLocation} from '@docusaurus/router';
|
||||
|
||||
// TODO not ideal, see also "useDocs"
|
||||
|
@ -49,7 +50,7 @@ export function DocsVersionProvider({
|
|||
export function useDocsVersion(): PropVersionMetadata {
|
||||
const version = useContext(DocsVersionContext);
|
||||
if (version === EmptyContextValue) {
|
||||
throw new Error('This hook requires usage of <DocsVersionProvider>');
|
||||
throw new ReactContextError('DocsVersionProvider');
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ export function DocsSidebarProvider({
|
|||
export function useDocsSidebar(): PropSidebar | null {
|
||||
const sidebar = useContext(DocsSidebarContext);
|
||||
if (sidebar === EmptyContextValue) {
|
||||
throw new Error('This hook requires usage of <DocsSidebarProvider>');
|
||||
throw new ReactContextError('DocsSidebarProvider');
|
||||
}
|
||||
return sidebar;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import React, {
|
|||
type ComponentType,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
|
||||
/*
|
||||
The idea behind all this is that a specific component must be able to fill a
|
||||
|
@ -60,9 +61,7 @@ export function MobileSecondaryMenuProvider({
|
|||
function useMobileSecondaryMenuContext(): ContextValue {
|
||||
const value = useContext(Context);
|
||||
if (value === null) {
|
||||
throw new Error(
|
||||
'MobileSecondaryMenuProvider was not used correctly, context value is null',
|
||||
);
|
||||
throw new ReactContextError('MobileSecondaryMenuProvider');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -41,3 +41,13 @@ export function useDynamicCallback<T extends (...args: never[]) => unknown>(
|
|||
// but good enough for our use
|
||||
return useCallback<T>((...args) => ref.current(...args), []);
|
||||
}
|
||||
|
||||
export class ReactContextError extends Error {
|
||||
constructor(providerName: string, additionalInfo?: string) {
|
||||
super();
|
||||
this.name = 'ReactContextError';
|
||||
this.message = `Hook ${
|
||||
this.stack?.split('\n')[1]?.match(/at (?<name>\w+)/)?.groups!.name
|
||||
} is called outside the <${providerName}>. ${additionalInfo || ''}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import React, {
|
|||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import {useDynamicCallback} from './reactUtils';
|
||||
import {useDynamicCallback, ReactContextError} from './reactUtils';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
|
||||
/**
|
||||
|
@ -76,9 +76,7 @@ export function ScrollControllerProvider({
|
|||
export function useScrollController(): ScrollController {
|
||||
const context = useContext(ScrollMonitorContext);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
'"useScrollController" is used but no context provider was found in the React tree.',
|
||||
);
|
||||
throw new ReactContextError('ScrollControllerProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import React, {
|
|||
type ReactNode,
|
||||
} from 'react';
|
||||
import {createStorageSlot, listStorageKeys} from './storageUtils';
|
||||
import {ReactContextError} from './reactUtils';
|
||||
|
||||
const TAB_CHOICE_PREFIX = 'docusaurus.tab.';
|
||||
|
||||
|
@ -82,9 +83,7 @@ export function TabGroupChoiceProvider({
|
|||
export function useTabGroupChoice(): TabGroupChoiceContextValue {
|
||||
const context = useContext(TabGroupChoiceContext);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
'"useUserPreferencesContext" is used outside of "Layout" component.',
|
||||
);
|
||||
throw new ReactContextError('TabGroupChoiceProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue