Commit 8ee4ceca authored by Robert Knight's avatar Robert Knight

Adapt to `useRef` type changes

`useRef(null as T|null)` now returns a `{ current: T|null }` instead of
`{ current: T }` as it did before. ie. it no longer drops the the null.
This makes sense but conflicted with a pattern we used in many places to
create a non-null ref: `useRef(/** @type {T|null} */ (null))`.

Resolve this by changing all non-nullable refs, for elements which are
set after the initial render, to cast the `useRef` result instead of the
init value.

```
const nonNullRef = /** @type {{ current: T }} */ (useRef());
```
parent 2bb54dcd
...@@ -33,6 +33,8 @@ function NotebookIframe({ config, groupId }) { ...@@ -33,6 +33,8 @@ function NotebookIframe({ config, groupId }) {
); );
} }
/** @typedef {import('../util/emitter').Emitter} Emitter */
/** /**
* @typedef NotebookModalProps * @typedef NotebookModalProps
* @prop {import('../util/emitter').EventBus} eventBus * @prop {import('../util/emitter').EventBus} eventBus
...@@ -53,9 +55,7 @@ export default function NotebookModal({ eventBus, config }) { ...@@ -53,9 +55,7 @@ export default function NotebookModal({ eventBus, config }) {
const [isHidden, setIsHidden] = useState(true); const [isHidden, setIsHidden] = useState(true);
const [groupId, setGroupId] = useState(/** @type {string|null} */ (null)); const [groupId, setGroupId] = useState(/** @type {string|null} */ (null));
const originalDocumentOverflowStyle = useRef(''); const originalDocumentOverflowStyle = useRef('');
const emitter = useRef( const emitterRef = useRef(/** @type {Emitter|null} */ (null));
/** @type {ReturnType<eventBus['createEmitter']>|null} */ (null)
);
// Stores the original overflow CSS property of document.body and reset it // Stores the original overflow CSS property of document.body and reset it
// when the component is destroyed // when the component is destroyed
...@@ -78,24 +78,22 @@ export default function NotebookModal({ eventBus, config }) { ...@@ -78,24 +78,22 @@ export default function NotebookModal({ eventBus, config }) {
}, [isHidden]); }, [isHidden]);
useEffect(() => { useEffect(() => {
emitter.current = eventBus.createEmitter(); const emitter = eventBus.createEmitter();
emitter.current.subscribe( emitter.subscribe('openNotebook', (/** @type {string} */ groupId) => {
'openNotebook', setIsHidden(false);
(/** @type {string} */ groupId) => { setIframeKey(iframeKey => iframeKey + 1);
setIsHidden(false); setGroupId(groupId);
setIframeKey(iframeKey => iframeKey + 1); });
setGroupId(groupId); emitterRef.current = emitter;
}
);
return () => { return () => {
emitter.current.destroy(); emitter.destroy();
}; };
}, [eventBus]); }, [eventBus]);
const onClose = () => { const onClose = () => {
setIsHidden(true); setIsHidden(true);
emitter.current.publish('closeNotebook'); emitterRef.current?.publish('closeNotebook');
}; };
if (groupId === null) { if (groupId === null) {
......
...@@ -10,7 +10,7 @@ import { TinyEmitter } from 'tiny-emitter'; ...@@ -10,7 +10,7 @@ import { TinyEmitter } from 'tiny-emitter';
* *
* @implements Destroyable * @implements Destroyable
*/ */
class Emitter { export class Emitter {
/** /**
* @param {TinyEmitter} emitter * @param {TinyEmitter} emitter
*/ */
......
...@@ -60,6 +60,9 @@ function AnnotationShareControl({ ...@@ -60,6 +60,9 @@ function AnnotationShareControl({
const closePanel = () => setOpen(false); const closePanel = () => setOpen(false);
// Interactions outside of the component when it is open should close it // Interactions outside of the component when it is open should close it
//
// @ts-expect-error - `useElementShouldClose` requires a non-nullable ref,
// but `shareRef` is nullable.
useElementShouldClose(shareRef, isOpen, closePanel); useElementShouldClose(shareRef, isOpen, closePanel);
useEffect(() => { useEffect(() => {
......
...@@ -87,7 +87,7 @@ function Excerpt({ ...@@ -87,7 +87,7 @@ function Excerpt({
useState(true); useState(true);
// Container for the excerpt's content. // Container for the excerpt's content.
const contentElement = useRef(/** @type {HTMLDivElement|null} */ (null)); const contentElement = /** @type {{ current: HTMLDivElement }} */ (useRef());
// Measured height of `contentElement` in pixels. // Measured height of `contentElement` in pixels.
const [contentHeight, setContentHeight] = useState(0); const [contentHeight, setContentHeight] = useState(0);
......
...@@ -402,15 +402,16 @@ export default function MarkdownEditor({ ...@@ -402,15 +402,16 @@ export default function MarkdownEditor({
useEffect(() => { useEffect(() => {
if (!preview) { if (!preview) {
input.current.focus(); input.current?.focus();
} }
}, [preview]); }, [preview]);
const togglePreview = () => setPreview(!preview); const togglePreview = () => setPreview(!preview);
const handleCommand = command => { const handleCommand = command => {
const inputEl = input.current; if (input.current) {
handleToolbarCommand(command, inputEl); handleToolbarCommand(command, input.current);
onEditText({ text: inputEl.value }); onEditText({ text: input.current.value });
}
}; };
const handleKeyDown = event => { const handleKeyDown = event => {
......
...@@ -28,7 +28,7 @@ export default function MarkdownView({ ...@@ -28,7 +28,7 @@ export default function MarkdownView({
() => (markdown ? renderMarkdown(markdown) : ''), () => (markdown ? renderMarkdown(markdown) : ''),
[markdown] [markdown]
); );
const content = useRef(/** @type {HTMLDivElement|null} */ (null)); const content = /** @type {{ current: HTMLDivElement }} */ (useRef());
useEffect(() => { useEffect(() => {
replaceLinksWithEmbeds(content.current, { replaceLinksWithEmbeds(content.current, {
......
...@@ -138,7 +138,7 @@ export default function Menu({ ...@@ -138,7 +138,7 @@ export default function Menu({
// //
// These handlers close the menu when the user taps or clicks outside the // These handlers close the menu when the user taps or clicks outside the
// menu or presses Escape. // menu or presses Escape.
const menuRef = useRef(/** @type {HTMLDivElement|null} */ (null)); const menuRef = /** @type {{ current: HTMLDivElement }} */ (useRef());
// Menu element should close via `closeMenu` whenever it's open and there // Menu element should close via `closeMenu` whenever it's open and there
// are user interactions outside of it (e.g. clicks) in the document // are user interactions outside of it (e.g. clicks) in the document
......
...@@ -81,9 +81,9 @@ export default function MenuItem({ ...@@ -81,9 +81,9 @@ export default function MenuItem({
const hasLeftIcon = icon || isSubmenuItem; const hasLeftIcon = icon || isSubmenuItem;
const hasRightIcon = icon && isSubmenuItem; const hasRightIcon = icon && isSubmenuItem;
const menuItemRef = useRef( const menuItemRef =
/** @type {(HTMLAnchorElement & HTMLDivElement)|null} */ (null) /** @type {{ current: HTMLAnchorElement & HTMLDivElement }} */ (useRef());
);
let focusTimer = null; let focusTimer = null;
let renderedIcon = null; let renderedIcon = null;
......
...@@ -28,7 +28,7 @@ export default function MenuKeyboardNavigation({ ...@@ -28,7 +28,7 @@ export default function MenuKeyboardNavigation({
children, children,
visible, visible,
}) { }) {
const menuRef = useRef(/** @type {HTMLDivElement|null} */ (null)); const menuRef = /** @type {{ current: HTMLDivElement }} */ (useRef());
useEffect(() => { useEffect(() => {
let focusTimer = null; let focusTimer = null;
......
...@@ -28,7 +28,7 @@ import { useStoreProxy } from '../store/use-store'; ...@@ -28,7 +28,7 @@ import { useStoreProxy } from '../store/use-store';
export default function SearchInput({ alwaysExpanded, query, onSearch }) { export default function SearchInput({ alwaysExpanded, query, onSearch }) {
const store = useStoreProxy(); const store = useStoreProxy();
const isLoading = store.isLoading(); const isLoading = store.isLoading();
const input = useRef(/** @type {HTMLInputElement|null} */ (null)); const input = /** @type {{ current: HTMLInputElement }} */ (useRef());
// The active filter query from the previous render. // The active filter query from the previous render.
const [prevQuery, setPrevQuery] = useState(query); const [prevQuery, setPrevQuery] = useState(query);
......
...@@ -19,7 +19,7 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; ...@@ -19,7 +19,7 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
* @param {SliderProps} props * @param {SliderProps} props
*/ */
export default function Slider({ children, visible }) { export default function Slider({ children, visible }) {
const containerRef = useRef(/** @type {HTMLDivElement|null} */ (null)); const containerRef = /** @type {{ current: HTMLDivElement }} */ (useRef());
const [containerHeight, setContainerHeight] = useState(visible ? 'auto' : 0); const [containerHeight, setContainerHeight] = useState(visible ? 'auto' : 0);
// Whether the content is currently partially or wholly visible. This is // Whether the content is currently partially or wholly visible. This is
......
...@@ -38,7 +38,7 @@ function TagEditor({ ...@@ -38,7 +38,7 @@ function TagEditor({
tagList, tagList,
tags: tagsService, tags: tagsService,
}) { }) {
const inputEl = useRef(/** @type {HTMLInputElement|null} */ (null)); const inputEl = /** @type {{ current: HTMLInputElement }} */ (useRef());
const [suggestions, setSuggestions] = useState(/** @type {string[]} */ ([])); const [suggestions, setSuggestions] = useState(/** @type {string[]} */ ([]));
const [activeItem, setActiveItem] = useState(-1); // -1 is unselected const [activeItem, setActiveItem] = useState(-1); // -1 is unselected
const [suggestionsListOpen, setSuggestionsListOpen] = useState(false); const [suggestionsListOpen, setSuggestionsListOpen] = useState(false);
...@@ -48,7 +48,7 @@ function TagEditor({ ...@@ -48,7 +48,7 @@ function TagEditor({
}); });
// Set up callback to monitor outside click events to close the AutocompleteList // Set up callback to monitor outside click events to close the AutocompleteList
const closeWrapperRef = useRef(/** @type {HTMLElement|null} */ (null)); const closeWrapperRef = /** @type {{ current: HTMLElement }} */ (useRef());
useElementShouldClose(closeWrapperRef, suggestionsListOpen, () => { useElementShouldClose(closeWrapperRef, suggestionsListOpen, () => {
setSuggestionsListOpen(false); setSuggestionsListOpen(false);
}); });
......
...@@ -144,5 +144,5 @@ export function useStoreProxy() { ...@@ -144,5 +144,5 @@ export function useStoreProxy() {
return cleanup; return cleanup;
}, [cache, store]); }, [cache, store]);
return proxy.current; return /** @type {SidebarStore} */ (proxy.current);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment