| Conditions | 17 | 
| Total Lines | 144 | 
| Code Lines | 107 | 
| Lines | 0 | 
| Ratio | 0 % | 
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like Modal.tsx ➔ Modal often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | import React, { createContext, useContext, useEffect, useRef } from "react"; | 
            ||
| 21 | |||
| 22 | export default function Modal({ | 
            ||
| 23 | id,  | 
            ||
| 24 | parentElement,  | 
            ||
| 25 | visible,  | 
            ||
| 26 | children,  | 
            ||
| 27 | className,  | 
            ||
| 28 | onModalConfirm,  | 
            ||
| 29 | onModalMiddle,  | 
            ||
| 30 | onModalCancel,  | 
            ||
| 31 | }: ModalProps): React.ReactPortal | null { | 
            ||
| 32 | // Set up div ref to measure modal height  | 
            ||
| 33 | const modalRef = useRef<HTMLDivElement>(null);  | 
            ||
| 34 | |||
| 35 | const getFocusableModalElements = () =>  | 
            ||
| 36 | modalRef && modalRef.current ? getFocusableElements(modalRef.current) : [];  | 
            ||
| 37 | |||
| 38 |   const handleTabKey = (e: KeyboardEvent): void => { | 
            ||
| 39 |     if (modalRef && modalRef.current) { | 
            ||
| 40 | const focusableModalElements = getFocusableModalElements();  | 
            ||
| 41 | |||
| 42 |       if (focusableModalElements.length === 0) { | 
            ||
| 43 | e.preventDefault(); // TODO: should this throw an error?  | 
            ||
| 44 | return;  | 
            ||
| 45 | }  | 
            ||
| 46 | |||
| 47 | const firstElement = focusableModalElements[0] as HTMLElement;  | 
            ||
| 48 | const lastElement = focusableModalElements[  | 
            ||
| 49 | focusableModalElements.length - 1  | 
            ||
| 50 | ] as HTMLElement;  | 
            ||
| 51 | |||
| 52 |       if (focusableModalElements.length === 1) { | 
            ||
| 53 | // This check to avoid strange behaviour if firstElement == lastElement.  | 
            ||
| 54 | firstElement.focus();  | 
            ||
| 55 | e.preventDefault();  | 
            ||
| 56 | return;  | 
            ||
| 57 | }  | 
            ||
| 58 | |||
| 59 | const focusableModalElementsArray = Array.from(focusableModalElements);  | 
            ||
| 60 | |||
| 61 | if (  | 
            ||
| 62 | document.activeElement &&  | 
            ||
| 63 | !focusableModalElementsArray.includes(  | 
            ||
| 64 | document.activeElement as HTMLElement,  | 
            ||
| 65 | )  | 
            ||
| 66 |       ) { | 
            ||
| 67 | firstElement.focus();  | 
            ||
| 68 | e.preventDefault();  | 
            ||
| 69 | return;  | 
            ||
| 70 | }  | 
            ||
| 71 | |||
| 72 |       if (!e.shiftKey && document.activeElement === lastElement) { | 
            ||
| 73 | firstElement.focus();  | 
            ||
| 74 | e.preventDefault();  | 
            ||
| 75 | return;  | 
            ||
| 76 | }  | 
            ||
| 77 | |||
| 78 |       if (e.shiftKey && document.activeElement === firstElement) { | 
            ||
| 79 | lastElement.focus();  | 
            ||
| 80 | e.preventDefault();  | 
            ||
| 81 | }  | 
            ||
| 82 | }  | 
            ||
| 83 | };  | 
            ||
| 84 | |||
| 85 | // Collection of key codes and event listeners  | 
            ||
| 86 | const keyListenersMap = new Map([  | 
            ||
| 87 | [27, onModalCancel],  | 
            ||
| 88 | [9, handleTabKey],  | 
            ||
| 89 | ]);  | 
            ||
| 90 | |||
| 91 | // Runs every time visible changes to set the overflow on the modal and update the body overflow  | 
            ||
| 92 |   useEffect((): (() => void) => { | 
            ||
| 93 |     function setBodyStyle(): void { | 
            ||
| 94 | document.body.style.overflow = visible ? "hidden" : "visible";  | 
            ||
| 95 | }  | 
            ||
| 96 | setBodyStyle();  | 
            ||
| 97 | // Runs on component unmount  | 
            ||
| 98 |     return (): void => { | 
            ||
| 99 | setBodyStyle();  | 
            ||
| 100 | };  | 
            ||
| 101 | }, [visible]);  | 
            ||
| 102 | |||
| 103 | // Adds various key commands to the modal  | 
            ||
| 104 |   useEffect((): (() => void) => { | 
            ||
| 105 | let keyListener;  | 
            ||
| 106 |     if (visible) { | 
            ||
| 107 |       keyListener = (e: KeyboardEvent): void => { | 
            ||
| 108 | const listener = keyListenersMap.get(e.keyCode);  | 
            ||
| 109 | return listener && listener(e);  | 
            ||
| 110 | };  | 
            ||
| 111 |       document.addEventListener("keydown", keyListener); | 
            ||
| 112 | }  | 
            ||
| 113 | |||
| 114 |     return (): void => { | 
            ||
| 115 |       if (keyListener !== undefined) { | 
            ||
| 116 |         document.removeEventListener("keydown", keyListener); | 
            ||
| 117 | }  | 
            ||
| 118 | };  | 
            ||
| 119 | }, [keyListenersMap, visible]);  | 
            ||
| 120 | |||
| 121 | // Focus the first focusable element when the modal becomes visible.  | 
            ||
| 122 |   useEffect(() => { | 
            ||
| 123 |     if (visible) { | 
            ||
| 124 | const focusableModalElements = getFocusableModalElements();  | 
            ||
| 125 |       if (focusableModalElements.length > 0) { | 
            ||
| 126 | const firstElement = focusableModalElements[0] as HTMLElement;  | 
            ||
| 127 | firstElement.focus();  | 
            ||
| 128 | }  | 
            ||
| 129 | }  | 
            ||
| 130 | }, [visible]);  | 
            ||
| 131 | |||
| 132 |   if (parentElement !== null) { | 
            ||
| 133 | return createPortal(  | 
            ||
| 134 | <div  | 
            ||
| 135 |         aria-describedby={`${id}-description`} | 
            ||
| 136 |         aria-hidden={!visible} | 
            ||
| 137 |         aria-labelledby={`${id}-title`} | 
            ||
| 138 |         data-c-dialog={visible ? "active--overflowing" : ""} | 
            ||
| 139 | data-c-padding="top(double) bottom(double)"  | 
            ||
| 140 | role="dialog"  | 
            ||
| 141 |         ref={modalRef} | 
            ||
| 142 |         className={className} | 
            ||
| 143 |         data-c-visibility={!visible ? "hidden" : ""} | 
            ||
| 144 | >  | 
            ||
| 145 | <div data-c-background="white(100)" data-c-radius="rounded">  | 
            ||
| 146 | <modalContext.Provider  | 
            ||
| 147 |             value={{ | 
            ||
| 148 | id,  | 
            ||
| 149 | parentElement,  | 
            ||
| 150 | visible,  | 
            ||
| 151 | onModalConfirm,  | 
            ||
| 152 | onModalMiddle,  | 
            ||
| 153 | onModalCancel,  | 
            ||
| 154 | }}  | 
            ||
| 155 | >  | 
            ||
| 156 |             {children} | 
            ||
| 157 | </modalContext.Provider>  | 
            ||
| 158 | </div>  | 
            ||
| 159 | </div>,  | 
            ||
| 160 | parentElement,  | 
            ||
| 161 | );  | 
            ||
| 162 | }  | 
            ||
| 163 | |||
| 164 | return null;  | 
            ||
| 165 | }  | 
            ||
| 247 |