No defaults for contexts (#3750)

in many places, we use a pattern like `React.createContext({} as
Editor)` when defining contexts. This causes a problem: `{}` is not
`Editor`, but you can still `useEditor` wherever you like and your code
with run with this confusing non-editor value.

This diff updates all our `createContext` calls to default to `null`,
with an explicit check and error for missing values. Now, if you
`useEditor` outside of `<Tldraw />`, you'll get a message telling you
that it can only be used within `<Tldraw />`.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features

### Release Notes

`useEditor` and other context-based hooks will now throw an error when
used out-of-context, instead of returning a fake value.
pull/3769/head
alex 2024-05-14 11:22:07 +01:00 zatwierdzone przez GitHub
rodzic ab807afda3
commit 5b21ad96ae
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
13 zmienionych plików z 47 dodań i 21 usunięć

Wyświetl plik

@ -1068,7 +1068,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
// @internal (undocumented)
export const EditorContext: React_2.Context<Editor>;
export const EditorContext: React_2.Context<Editor | null>;
// @public (undocumented)
export class Ellipse2d extends Geometry2d {

Wyświetl plik

@ -2,9 +2,15 @@ import React from 'react'
import { Editor } from '../editor/Editor'
/** @internal */
export const EditorContext = React.createContext({} as Editor)
export const EditorContext = React.createContext<Editor | null>(null)
/** @public */
export function useEditor(): Editor {
return React.useContext(EditorContext)
const editor = React.useContext(EditorContext)
if (!editor) {
throw new Error(
'useEditor must be used inside of the <Tldraw /> or <TldrawEditor /> components'
)
}
return editor
}

Wyświetl plik

@ -86,7 +86,7 @@ export type TLEditorComponents = Partial<
} & ErrorComponents
>
const EditorComponentsContext = createContext({} as TLEditorComponents & ErrorComponents)
const EditorComponentsContext = createContext<null | (TLEditorComponents & ErrorComponents)>(null)
type ComponentsContextProviderProps = {
overrides?: TLEditorComponents
@ -140,5 +140,9 @@ export function EditorComponentsProvider({
/** @public */
export function useEditorComponents() {
return useContext(EditorComponentsContext)
const components = useContext(EditorComponentsContext)
if (!components) {
throw new Error('useEditorComponents must be used inside of <EditorComponentsProvider />')
}
return components
}

Wyświetl plik

@ -2566,7 +2566,7 @@ export function useCanUndo(): boolean;
export function useCopyAs(): (ids: TLShapeId[], format?: TLCopyType) => void;
// @public (undocumented)
export const useCurrentTranslation: () => TLUiTranslation;
export function useCurrentTranslation(): TLUiTranslation;
// @public (undocumented)
export function useDefaultColorTheme(): {

Wyświetl plik

@ -16,11 +16,15 @@ export type TldrawUiMenuContextType =
const menuContext = createContext<{
type: TldrawUiMenuContextType
sourceId: TLUiEventSource
}>({ type: 'menu', sourceId: 'main-menu' })
} | null>(null)
/** @public */
export function useTldrawUiMenuContext() {
return useContext(menuContext)
const context = useContext(menuContext)
if (!context) {
throw new Error('useTldrawUiMenuContext must be used within a TldrawUiMenuContextProvider')
}
return context
}
/** @public */

Wyświetl plik

@ -56,7 +56,7 @@ export interface TLUiActionItem<
export type TLUiActionsContextType = Record<string, TLUiActionItem>
/** @internal */
export const ActionsContext = React.createContext<TLUiActionsContextType>({})
export const ActionsContext = React.createContext<TLUiActionsContextType | null>(null)
/** @public */
export type ActionsProviderProps = {

Wyświetl plik

@ -2,7 +2,7 @@ import { useEditor, useValue } from '@tldraw/editor'
import React, { ReactNode, useContext } from 'react'
import { PORTRAIT_BREAKPOINT, PORTRAIT_BREAKPOINTS } from '../constants'
const BreakpointContext = React.createContext(0)
const BreakpointContext = React.createContext<number | null>(null)
/** @public */
export function BreakPointProvider({
@ -40,5 +40,9 @@ export function BreakPointProvider({
/** @public */
export function useBreakpoint() {
return useContext(BreakpointContext)
const breakpoint = useContext(BreakpointContext)
if (breakpoint === null) {
throw new Error('useBreakpoint must be used inside of the <BreakpointProvider /> component')
}
return breakpoint
}

Wyświetl plik

@ -58,7 +58,7 @@ export type TLUiComponents = Partial<{
[K in keyof BaseTLUiComponents]: BaseTLUiComponents[K] | null
}>
const TldrawUiComponentsContext = createContext({} as TLUiComponents)
const TldrawUiComponentsContext = createContext<TLUiComponents | null>(null)
/** @public */
export type TLUiComponentsProviderProps = {
@ -105,5 +105,9 @@ export function TldrawUiComponentsProvider({
/** @public */
export function useTldrawUiComponents() {
return useContext(TldrawUiComponentsContext)
const components = useContext(TldrawUiComponentsContext)
if (!components) {
throw new Error('useTldrawUiComponents must be used within a TldrawUiComponentsProvider')
}
return components
}

Wyświetl plik

@ -24,7 +24,7 @@ export type TLUiDialogsContextType = {
}
/** @internal */
export const DialogsContext = createContext({} as TLUiDialogsContextType)
export const DialogsContext = createContext<TLUiDialogsContextType | null>(null)
/** @internal */
export type DialogsProviderProps = {

Wyświetl plik

@ -115,7 +115,7 @@ const defaultEventHandler: TLUiEventHandler = () => void null
export type TLUiEventContextType = TLUiEventHandler<keyof TLUiEventMap>
/** @internal */
export const EventsContext = React.createContext<TLUiEventContextType>({} as TLUiEventContextType)
export const EventsContext = React.createContext<TLUiEventContextType | null>(null)
/** @public */
export type EventsProviderProps = {

Wyświetl plik

@ -33,7 +33,7 @@ export type TLUiToastsContextType = {
}
/** @internal */
export const ToastsContext = createContext({} as TLUiToastsContextType)
export const ToastsContext = createContext<TLUiToastsContextType | null>(null)
/** @internal */
export type ToastsProviderProps = {

Wyświetl plik

@ -28,7 +28,7 @@ export interface TLUiToolItem<
export type TLUiToolsContextType = Record<string, TLUiToolItem>
/** @internal */
export const ToolsContext = React.createContext({} as TLUiToolsContextType)
export const ToolsContext = React.createContext<null | TLUiToolsContextType>(null)
/** @public */
export type TLUiToolsProviderProps = {

Wyświetl plik

@ -23,12 +23,16 @@ export interface TLUiTranslationProviderProps {
/** @public */
export type TLUiTranslationContextType = TLUiTranslation
const TranslationsContext = React.createContext<TLUiTranslationContextType>(
{} as TLUiTranslationContextType
)
const TranslationsContext = React.createContext<TLUiTranslationContextType | null>(null)
/** @public */
export const useCurrentTranslation = () => React.useContext(TranslationsContext)
export function useCurrentTranslation() {
const translations = React.useContext(TranslationsContext)
if (!translations) {
throw new Error('useCurrentTranslation must be used inside of <TldrawUiContextProvider />')
}
return translations
}
/**
* Provides a translation context to the editor.