v2/Input/TreeInput/index.tsx
import React, { useCallback, useMemo, useState,} from 'react';import { _cs, isNotDefined, Obj } from '@togglecorp/fujs'; import { OptionKey } from '../../types'; import Button from '../../Action/Button';import List from '../../View/List';import Checkbox from '../Checkbox';import HintAndError from '../HintAndError';import Label from '../Label';import { generateExtendedRelations, ExtendedRelation } from './utils'; import styles from './styles.scss'; interface TreeNodeProps<T, K extends OptionKey> { className?: string; keySelector: (datum: T) => K; parentKeySelector: (datum: T) => K | undefined; labelSelector: (datum: T) => string | number; onChange: (keys: K[]) => void; value: K[]; nodeKey: K; nodeLabel: string | number; disabled: boolean; readOnly: boolean; defaultCollapseLevel: number; level: number; relations: Obj<ExtendedRelation<T, K> | undefined>;} Function `TreeNode` has 139 lines of code (exceeds 100 allowed). Consider refactoring.
Function `TreeNode` has a Cognitive Complexity of 16 (exceeds 10 allowed). Consider refactoring.function TreeNode<T, K extends OptionKey>(props: TreeNodeProps<T, K>) { const { className, disabled, readOnly, nodeKey, nodeLabel, value, labelSelector, parentKeySelector, keySelector, level, defaultCollapseLevel, onChange, relations, } = props; const [collapsed, setCollapsed] = useState(level >= defaultCollapseLevel); const relation = relations[String(nodeKey)]; const allOwnOptions = relation ? relation.children : undefined; const ownOptions = useMemo( () => allOwnOptions && allOwnOptions.filter( option => parentKeySelector(option) === nodeKey, ), [allOwnOptions, parentKeySelector, nodeKey], ); const isLeaf = ownOptions && ownOptions.length <= 0; const someSelected = useMemo( () => ownOptions && ownOptions.some((option) => { const key = keySelector(option);FIXME found // FIXME: create a mapping to optimize check const selected = value.includes(key); return selected; }), [value, keySelector, ownOptions], ); FIXME found // FIXME: create a mapping to optimize check const checked = value.includes(nodeKey); const handleCollapseOption = useCallback( () => { setCollapsed(true); }, [], ); const handleToggleCollapseOption = useCallback( () => { setCollapsed(v => !v); }, [], ); const handleCheckboxChange = useCallback( (val: boolean) => { const oldKeys = new Set(value); if (val) { // NOTE: Add current node oldKeys.add(nodeKey); if (allOwnOptions) { // NOTE: Add all children nodes allOwnOptions.forEach((option) => { oldKeys.add(keySelector(option)); }); } } else { // NOTE: Remove current node oldKeys.delete(nodeKey); // NOTE: Remove all children nodes if (allOwnOptions) { allOwnOptions.forEach((option) => { oldKeys.delete(keySelector(option)); }); } } onChange([...oldKeys]); }, [onChange, value, nodeKey, keySelector, allOwnOptions], ); const handleTreeNodeChange = useCallback( (newKeys: K[]) => { // if all child keys are selected, then select current as well const allChildSelected = ownOptions && ownOptions.every((item) => { const itemKey = keySelector(item);FIXME found // FIXME: create a mapping to optimize check const selected = newKeys.includes(itemKey); return selected; }); if (allChildSelected) { onChange([...newKeys, nodeKey]);FIXME found // FIXME: create a mapping to optimize check } else if (newKeys.includes(nodeKey)) { // if not all child selected && current key is there const filteredKeys = newKeys.filter(key => key !== nodeKey); onChange(filteredKeys); } else { onChange(newKeys); } }, [onChange, keySelector, ownOptions, nodeKey], ); return ( <div className={_cs(styles.treeNode, className, collapsed && styles.collapsed)}> <div className={styles.left}> <Button className={styles.expandButton} disabled={isLeaf} onClick={handleToggleCollapseOption} transparent iconName="arrowDropright" /> {!collapsed && !isLeaf && ( <div className={styles.stem} role="button" onClick={handleCollapseOption} onKeyDown={handleCollapseOption} tabIndex={-1} > <div className={styles.line} /> </div> )} </div> <div className={styles.right}> <Checkbox className={styles.checkbox} labelClassName={styles.label} checkIconClassName={styles.checkIcon} value={checked} label={nodeLabel} disabled={disabled} readOnly={readOnly} onChange={handleCheckboxChange} indeterminate={someSelected} /> { !isLeaf && ( <TreeNodeList relations={relations} className={styles.nodeList} visibleOptions={ownOptions} keySelector={keySelector} disabled={disabled} readOnly={readOnly} labelSelector={labelSelector} parentKeySelector={parentKeySelector} value={value} defaultCollapseLevel={defaultCollapseLevel} level={level + 1} onChange={handleTreeNodeChange} /> )} </div> </div> );} interface TreeNodeListProps<T, K extends OptionKey> { className?: string; keySelector: (datum: T) => K; parentKeySelector: (datum: T) => K | undefined; labelSelector: (datum: T) => string | number; onChange: (keys: K[]) => void; value: K[]; visibleOptions: T[]; disabled: boolean; readOnly: boolean; defaultCollapseLevel: number; level: number; relations: Obj<ExtendedRelation<T, K> | undefined>;}function TreeNodeList<T, K extends OptionKey>(props: TreeNodeListProps<T, K>) { const { className, // options, keySelector, disabled, readOnly, labelSelector, parentKeySelector, value, onChange, // childOptions, visibleOptions, level, defaultCollapseLevel, relations, } = props; const rendererParams = useCallback( (key: K, v: T) => ({ disabled, readOnly, nodeLabel: labelSelector(v), nodeKey: key, // For children keySelector, labelSelector, parentKeySelector, value, defaultCollapseLevel, level, onChange, relations, }), [ value, onChange, relations, readOnly, disabled, defaultCollapseLevel, level, keySelector, labelSelector, parentKeySelector, ], ); return ( <div className={_cs(styles.treeNodeList, className)}> <List keySelector={keySelector} data={visibleOptions} renderer={TreeNode} rendererParams={rendererParams} /> </div> );}TreeNodeList.defaultProps = { visibleOptions: [],}; export interface TreeProps<T, K extends OptionKey> { // autoFocus?: boolean; className?: string; disabled: boolean; error?: string; hint?: string; keySelector: (datum: T) => K; parentKeySelector: (datum: T) => K | undefined; label?: string; labelClassName?: string; labelSelector: (datum: T) => string | number; onChange: (keys: K[]) => void; options: T[]; readOnly: boolean; showHintAndError: boolean; showLabel: boolean; title?: string; value: K[]; defaultCollapseLevel: number; labelRightComponent?: React.ReactNode; labelRightComponentClassName?: string;} function TreeInput<T, K extends OptionKey = string>(props: TreeProps<T, K>) { const { className: classNameFromProps, disabled, error, hint, label, labelClassName, labelRightComponent, labelRightComponentClassName, showHintAndError, showLabel, title, keySelector, parentKeySelector, labelSelector, onChange, options, readOnly, value, defaultCollapseLevel, } = props; const className = _cs( classNameFromProps, 'tree', disabled && 'disabled', error && 'error', ); const visibleOptions = useMemo( () => options.filter((option) => { const parentKey = parentKeySelector(option); return isNotDefined(parentKey); }), [options, parentKeySelector], ); const relations = useMemo( () => generateExtendedRelations( options, keySelector, parentKeySelector, ), [options, keySelector, parentKeySelector], ); return ( <div className={className} title={title} > {showLabel && ( <Label className={labelClassName} disabled={disabled} error={!!error} title={label} rightComponent={labelRightComponent} rightComponentClassName={labelRightComponentClassName} > {label} </Label> )} <TreeNodeList className={styles.nodeList} defaultCollapseLevel={defaultCollapseLevel} level={0} readOnly={readOnly} disabled={disabled} keySelector={keySelector} parentKeySelector={parentKeySelector} labelSelector={labelSelector} value={value} relations={relations} onChange={onChange} visibleOptions={visibleOptions} /> {showHintAndError && ( <HintAndError error={error} hint={hint} /> )} </div> );}TreeInput.defaultProps = { disabled: false, readOnly: false, showHintAndError: true, showLabel: true, value: [], options: [], defaultCollapseLevel: 1,}; export default TreeInput;