Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplified code by using the includes method to check if an option is… #677

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/hooks/use-multi-select.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";

import { Option, SelectProps } from "../lib/interfaces";

Expand Down Expand Up @@ -29,7 +29,7 @@ interface MultiSelectContextProps extends SelectProps {

interface MultiSelectProviderProps {
props: SelectProps;
children;
children: React.ReactNode;
}

const MultiSelectContext = React.createContext<MultiSelectContextProps>(
Expand All @@ -41,7 +41,10 @@ export const MultiSelectProvider = ({
children,
}: MultiSelectProviderProps) => {
const [options, setOptions] = useState(props.options);
const t = (key) => props.overrideStrings?.[key] || defaultStrings[key];
const t = useMemo(
() => (key) => props.overrideStrings?.[key] || defaultStrings[key],
[props.overrideStrings]
);

useEffect(() => {
setOptions(props.options);
Expand Down
15 changes: 9 additions & 6 deletions src/multi-select/arrow.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from "react";
interface IArrowProps {
expanded: boolean | undefined;
}

export const Arrow = ({ expanded }) => (
export const Arrow = ({ expanded }: IArrowProps) => (
<svg
width="24"
height="24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="dropdown-heading-dropdown-arrow gray"
className="rmsc__arrow-svg dropdown-heading-dropdown-arrow gray"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using inline styles for the fill and stroke attributes, consider using CSS classes to style the SVG. This would allow you to more easily customize the appearance of the arrow and make the component more reusable.

>
<path d={expanded ? "M18 15 12 9 6 15" : "M6 9L12 15 18 9"} />
<path
className="rmsc__arrow-path"
d={expanded ? "M18 15 12 9 6 15" : "M6 9L12 15 18 9"}
/>
</svg>
);
29 changes: 16 additions & 13 deletions src/multi-select/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* and hosts it in the component. When the component is selected, it
* drops-down the contentComponent and applies the contentProps.
*/
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";

import { useDidUpdateEffect } from "../hooks/use-did-update-effect";
import { useKey } from "../hooks/use-key";
Expand Down Expand Up @@ -43,7 +43,7 @@ const Dropdown = () => {
const [hasFocus, setHasFocus] = useState(false);
const FinalArrow = ArrowRenderer || Arrow;

const wrapper: any = useRef();
const wrapperRef = useRef<HTMLDivElement>(null);

useDidUpdateEffect(() => {
onMenuToggle && onMenuToggle(expanded);
Expand All @@ -54,7 +54,7 @@ const Dropdown = () => {
setIsInternalExpand(false);
setExpanded(isOpen);
}
}, [isOpen]);
}, [isOpen, defaultIsOpen]);

const handleKeyDown = (e) => {
// allows space and enter when focused on input/button
Expand All @@ -68,7 +68,7 @@ const Dropdown = () => {
if (isInternalExpand) {
if (e.code === KEY.ESCAPE) {
setExpanded(false);
wrapper?.current?.focus();
wrapperRef?.current?.focus();
} else {
setExpanded(true);
}
Expand All @@ -77,11 +77,11 @@ const Dropdown = () => {
};

useKey([KEY.ENTER, KEY.ARROW_DOWN, KEY.SPACE, KEY.ESCAPE], handleKeyDown, {
target: wrapper,
target: wrapperRef,
});

const handleHover = (iexpanded: boolean) => {
isInternalExpand && shouldToggleOnHover && setExpanded(iexpanded);
const handleHover = (isExpanded: boolean) => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed spelling mistake

isInternalExpand && shouldToggleOnHover && setExpanded(isExpanded);
};

const handleFocus = () => !hasFocus && setHasFocus(true);
Expand All @@ -101,11 +101,14 @@ const Dropdown = () => {
isInternalExpand && setExpanded(isLoading || disabled ? false : !expanded);
};

const handleClearSelected = (e) => {
e.stopPropagation();
onChange([]);
isInternalExpand && setExpanded(false);
};
const handleClearSelected = useCallback(
(e) => {
e.stopPropagation();
onChange([]);
isInternalExpand && setExpanded(false);
},
[onChange, isInternalExpand]
);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will avoid creating a new function on every render, which can improve performance

return (
<div
Expand All @@ -115,7 +118,7 @@ const Dropdown = () => {
aria-expanded={expanded}
aria-readonly={true}
aria-disabled={disabled}
ref={wrapper}
ref={wrapperRef}
onFocus={handleFocus}
onBlur={handleBlur}
onMouseEnter={handleMouseEnter}
Expand Down
29 changes: 18 additions & 11 deletions src/multi-select/header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useMemo } from "react";

import { useMultiSelect } from "../hooks/use-multi-select";

Expand All @@ -7,16 +7,23 @@ export const DropdownHeader = () => {

const noneSelected = value.length === 0;
const allSelected = value.length === options.length;
const customText = valueRenderer && valueRenderer(value, options);
const selectedText = valueRenderer
? valueRenderer(value, options)
: "Text Undefined";

const getSelectedText = () => value.map((s) => s.label).join(", ");

return noneSelected ? (
<span className="gray">{customText || t("selectSomeItems")}</span>
) : (
<span>
{customText ||
(allSelected ? t("allItemsAreSelected") : getSelectedText())}
</span>
const getSelectedText = useMemo(
() => () => value.map((s) => s.label).join(", "),
[value]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the useMemo hook to memoize the getSelectedText function, to avoid recomputing it on every render.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you overcomplicating this by returning a function from useMemo ?

Could be simply a string returned from useMemo with no invocation

);

switch (true) {
case noneSelected:
return (
<span className="gray">{selectedText || t("selectSomeItems")}</span>
);
case allSelected:
return <span>{selectedText || t("allItemsAreSelected")}</span>;
default:
return <span>{selectedText || getSelectedText()}</span>;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a ternary operator to determine the text to display, consider using a switch statement. This would make the code easier to read and understand, and would allow you to add additional cases more easily.

};
9 changes: 3 additions & 6 deletions src/select-panel/cross.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@ export const Cross = () => (
<svg
width="24"
height="24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="dropdown-search-clear-icon gray"
className="rmsc__cross-svg gray dropdown-search-clear-icon gray"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
<line className="rmsc__cross-line" x1="18" y1="6" x2="6" y2="18" />
<line className="rmsc__cross-line" x1="6" y1="6" x2="18" y2="18" />
</svg>
);
10 changes: 7 additions & 3 deletions src/select-panel/default-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface IDefaultItemRendererProps {
checked: boolean;
option: Option;
disabled?: boolean;
onClick;
onClick: () => void;
}

const DefaultItemRenderer = ({
Expand All @@ -15,16 +15,20 @@ const DefaultItemRenderer = ({
onClick,
disabled,
}: IDefaultItemRendererProps) => (
<div className={`item-renderer ${disabled ? "disabled" : ""}`}>
<label
className={`item-renderer ${disabled ? "disabled" : ""}`}
htmlFor={option.value}
>
<input
id={option.value}
type="checkbox"
onChange={onClick}
checked={checked}
tabIndex={-1}
disabled={disabled}
/>
<span>{option.label}</span>
</div>
</label>
);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a more semantic HTML element for the label, such as a label element. This would improve the accessibility of the component and make it easier to style


export default DefaultItemRenderer;
21 changes: 12 additions & 9 deletions src/select-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* user selects the component. It encapsulates the search filter, the
* Select-all item, and the list of options.
*/
import React, {
import React, {
useCallback,
useEffect,
useMemo,
Expand Down Expand Up @@ -43,14 +43,17 @@ const SelectPanel = () => {
onCreateOption,
} = useMultiSelect();

const listRef = useRef<any>();
const searchInputRef = useRef<any>();
const listRef = useRef<any>(null);
const searchInputRef = useRef<HTMLInputElement | null>(null);
const [searchText, setSearchText] = useState("");
const [filteredOptions, setFilteredOptions] = useState(options);
const [searchTextForFilter, setSearchTextForFilter] = useState("");
const [focusIndex, setFocusIndex] = useState(0);
const debouncedSearch = useCallback(
debounce((query) => setSearchTextForFilter(query), debounceDuration),
debounce(
(query: string) => setSearchTextForFilter(query),
debounceDuration
),
[]
);

Expand All @@ -68,7 +71,7 @@ const SelectPanel = () => {
value: "",
};

const selectAllValues = (checked) => {
const selectAllValues = (checked: boolean) => {
const filteredValues = filteredOptions
.filter((o) => !o.disabled)
.map((o) => o.value);
Expand All @@ -90,13 +93,13 @@ const SelectPanel = () => {
onChange(newOptions);
};

const handleSearchChange = (e) => {
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
debouncedSearch(e.target.value);
setSearchText(e.target.value);
setFocusIndex(FocusType.SEARCH);
};

const handleClear = () => {
const handleClear = (): void => {
setSearchTextForFilter("");
setSearchText("");
searchInputRef?.current?.focus();
Expand All @@ -105,7 +108,7 @@ const SelectPanel = () => {
const handleItemClicked = (index: number) => setFocusIndex(index);

// Arrow Key Navigation
const handleKeyDown = (e) => {
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.code) {
case KEY.ARROW_UP:
updateFocus(-1);
Expand Down Expand Up @@ -174,7 +177,7 @@ const SelectPanel = () => {
getFilteredOptions().then(setFilteredOptions);
}, [searchTextForFilter, options]);

const creationRef: any = useRef();
const creationRef: any = useRef(null);
useKey([KEY.ENTER], handleOnCreateOption, { target: creationRef });

const showCreatable =
Expand Down
2 changes: 1 addition & 1 deletion src/select-panel/select-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const SelectList = ({ options, onClick, skipIndex }: ISelectListProps) => {
tabIndex={tabIndex}
option={o}
onSelectionChanged={(c) => handleSelectionChanged(o, c)}
checked={!!value.find((s) => s.value === o.value)}
checked={value.includes(o)}
onClick={(e) => onClick(e, tabIndex)}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplify code by using the include method.

itemRenderer={ItemRenderer}
disabled={o.disabled || disabled}
Expand Down
12 changes: 12 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@
animation: dash 1.5s ease-in-out infinite;
}

.rmsc__arrow-svg {
fill: none;
stroke: currentColor;
stroke-width: 2;
}

.rmsc__cross-svg {
fill: none;
stroke: currentColor;
stroke-width: 2;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style SVG element using CSS value instead of writing hardcoded code. now, the user could override this value if he wants.

@keyframes rotate {
100% {
transform: rotate(360deg);
Expand Down