Skip to content

Commit 4589704

Browse files
authored
Always accept null as part of a RefObject
1 parent 5654881 commit 4589704

File tree

2 files changed

+86
-82
lines changed

2 files changed

+86
-82
lines changed
Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,120 @@
1-
import React, { useRef, useLayoutEffect, useState, useCallback } from 'react'
2-
export type SelectionState = readonly [number, number]
1+
import React, { useRef, useLayoutEffect, useState, useCallback } from "react";
2+
export type SelectionState = readonly [number, number];
33

44
type TransformInput = {
5-
current: Readonly<SelectionState>
6-
previous: Readonly<SelectionState>
7-
value: string
8-
direction: 'forward' | 'backward' | 'none' | null
9-
}
5+
current: Readonly<SelectionState>;
6+
previous: Readonly<SelectionState>;
7+
value: string;
8+
direction: "forward" | "backward" | "none" | null;
9+
};
1010

1111
// TODO: fix firefox direction
12-
type Transform = (input: TransformInput) => [number, number] | null
12+
type Transform = (input: TransformInput) => [number, number] | null;
1313

1414
const transform: Transform = ({ current, previous, value }) => {
15-
if (current[0] !== current[1]) return null
16-
if (typeof current[0] !== 'number') return null
17-
if (typeof current[1] !== 'number') return null
15+
if (current[0] !== current[1]) return null;
16+
if (typeof current[0] !== "number") return null;
17+
if (typeof current[1] !== "number") return null;
1818

19-
const [start, end] = current
19+
const [start, end] = current;
2020

2121
if (start > 0 && previous[0] === start && previous[1] === start + 1) {
22-
return [start - 1, end]
22+
return [start - 1, end];
2323
}
2424

2525
if (value[start]?.length) {
26-
return [start, end + 1]
26+
return [start, end + 1];
2727
}
2828

2929
// TODO: add switch prop for this behaviour
3030
// if (eq(current, [input.maxLength, input.maxLength])) {
3131
// return [input.maxLength -1, input.maxLength]
3232
// }
3333

34-
return null
35-
}
34+
return null;
35+
};
3636

3737
const getSelectionState = (input: HTMLInputElement): SelectionState => {
38-
return [+input.selectionStart!, +input.selectionEnd!]
39-
}
38+
return [+input.selectionStart!, +input.selectionEnd!];
39+
};
4040

41-
const ZERO: SelectionState = [0, 0]
41+
const ZERO: SelectionState = [0, 0];
4242
const eq = (a: SelectionState, b: SelectionState): boolean => {
43-
return a[0] === b[0] && a[1] === b[1]
44-
}
43+
return a[0] === b[0] && a[1] === b[1];
44+
};
4545

4646
const useCodeInputHandler = ({
4747
inputRef,
4848
previousRef,
4949
setSelection,
5050
}: {
51-
inputRef: React.RefObject<HTMLInputElement>
52-
previousRef: React.MutableRefObject<SelectionState | undefined>
53-
setSelection: React.Dispatch<React.SetStateAction<SelectionState>>
51+
inputRef: React.RefObject<HTMLInputElement | null>;
52+
previousRef: React.MutableRefObject<SelectionState | undefined>;
53+
setSelection: React.Dispatch<React.SetStateAction<SelectionState>>;
5454
}) => {
5555
return useCallback(
5656
({ type }: { type: string }) => {
57-
const input = inputRef.current
58-
const previous = previousRef.current
59-
if (!previous || !input) return
57+
const input = inputRef.current;
58+
const previous = previousRef.current;
59+
if (!previous || !input) return;
6060

61-
const { selectionDirection: direction, value } = input
62-
const current = getSelectionState(input)
61+
const { selectionDirection: direction, value } = input;
62+
const current = getSelectionState(input);
6363

6464
const save = (selection: SelectionState): void => {
6565
if (eq(selection, previous)) {
66-
if (eq(selection, ZERO)) return
67-
if (eq(selection, getSelectionState(input))) return
66+
if (eq(selection, ZERO)) return;
67+
if (eq(selection, getSelectionState(input))) return;
6868
}
69-
previousRef.current = selection
70-
setSelection((state) => (eq(state, selection) ? state : selection))
71-
input.setSelectionRange(...selection, direction || undefined)
72-
}
69+
previousRef.current = selection;
70+
setSelection((state) => (eq(state, selection) ? state : selection));
71+
input.setSelectionRange(...selection, direction || undefined);
72+
};
7373

74-
if (type === 'selectionchange' && document.activeElement !== input) {
75-
return save([value.length, value.length] as const)
74+
if (type === "selectionchange" && document.activeElement !== input) {
75+
return save([value.length, value.length] as const);
7676
}
7777

78-
save(transform({ previous, current, direction, value }) || current)
78+
save(transform({ previous, current, direction, value }) || current);
7979
},
80-
[inputRef, previousRef, setSelection],
81-
)
82-
}
80+
[inputRef, previousRef, setSelection]
81+
);
82+
};
8383

8484
const useCodeInputEffect = ({
8585
inputRef,
8686
previousRef,
8787
handler,
8888
}: {
89-
inputRef: React.RefObject<HTMLInputElement>
90-
previousRef: React.MutableRefObject<SelectionState | undefined>
91-
handler: (event: Event) => void
89+
inputRef: React.RefObject<HTMLInputElement | null>;
90+
previousRef: React.MutableRefObject<SelectionState | undefined>;
91+
handler: (event: Event) => void;
9292
}): void => {
9393
useLayoutEffect(() => {
94-
const input = inputRef.current
94+
const input = inputRef.current;
9595

9696
if (previousRef.current === undefined && input) {
97-
previousRef.current = getSelectionState(input)
97+
previousRef.current = getSelectionState(input);
9898
}
9999

100-
const handlerRef = handler // closure ref to added handler
101-
input?.addEventListener('input', handlerRef)
102-
document.addEventListener('selectionchange', handlerRef)
100+
const handlerRef = handler; // closure ref to added handler
101+
input?.addEventListener("input", handlerRef);
102+
document.addEventListener("selectionchange", handlerRef);
103103
return () => {
104-
input?.removeEventListener('input', handlerRef)
105-
document.removeEventListener('selectionchange', handlerRef)
106-
}
107-
}, [inputRef, handler, previousRef])
108-
}
109-
110-
export const useCodeInput = (inputRef: React.RefObject<HTMLInputElement>) => {
111-
const [selection, setSelection] = useState<SelectionState>(ZERO)
112-
const previousRef = useRef<SelectionState>()
113-
const handler = useCodeInputHandler({ inputRef, previousRef, setSelection })
114-
115-
useCodeInputEffect({ inputRef, previousRef, handler })
116-
117-
return selection
118-
}
104+
input?.removeEventListener("input", handlerRef);
105+
document.removeEventListener("selectionchange", handlerRef);
106+
};
107+
}, [inputRef, handler, previousRef]);
108+
};
109+
110+
export const useCodeInput = (
111+
inputRef: React.RefObject<HTMLInputElement | null>
112+
) => {
113+
const [selection, setSelection] = useState<SelectionState>(ZERO);
114+
const previousRef = useRef<SelectionState>();
115+
const handler = useCodeInputHandler({ inputRef, previousRef, setSelection });
116+
117+
useCodeInputEffect({ inputRef, previousRef, handler });
118+
119+
return selection;
120+
};
Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
1-
import React, { useEffect, useRef, useState } from 'react'
1+
import React, { useEffect, useRef, useState } from "react";
22

3-
export const useIsFocused = (inputRef: React.RefObject<HTMLInputElement>) => {
4-
const [isFocused, setIsFocused] = useState<boolean | undefined>(undefined)
5-
const isFocusedRef = useRef<boolean | undefined>(isFocused)
3+
export const useIsFocused = (
4+
inputRef: React.RefObject<HTMLInputElement | null>
5+
) => {
6+
const [isFocused, setIsFocused] = useState<boolean | undefined>(undefined);
7+
const isFocusedRef = useRef<boolean | undefined>(isFocused);
68

7-
isFocusedRef.current = isFocused
9+
isFocusedRef.current = isFocused;
810

911
useEffect(() => {
10-
const input = inputRef.current
11-
if (!input) return
12+
const input = inputRef.current;
13+
if (!input) return;
1214

13-
const onFocus = () => setIsFocused(true)
14-
const onBlur = () => setIsFocused(false)
15-
input.addEventListener('focus', onFocus)
16-
input.addEventListener('blur', onBlur)
15+
const onFocus = () => setIsFocused(true);
16+
const onBlur = () => setIsFocused(false);
17+
input.addEventListener("focus", onFocus);
18+
input.addEventListener("blur", onBlur);
1719

1820
if (isFocusedRef.current === undefined)
19-
setIsFocused(document.activeElement === input)
21+
setIsFocused(document.activeElement === input);
2022

2123
return () => {
22-
input.removeEventListener('focus', onFocus)
23-
input.removeEventListener('blur', onBlur)
24-
}
25-
}, [inputRef, setIsFocused])
24+
input.removeEventListener("focus", onFocus);
25+
input.removeEventListener("blur", onBlur);
26+
};
27+
}, [inputRef, setIsFocused]);
2628

27-
return isFocused
28-
}
29+
return isFocused;
30+
};

0 commit comments

Comments
 (0)