DEV Community

Cover image for Create custom select component in React(Compound components pattern + Typescript)
Ashwin kumar
Ashwin kumar

Posted on

Create custom select component in React(Compound components pattern + Typescript)

Hai folks. In this blog we are going to build a custom select component using the compound components pattern. If you are not familiar with what are compound components pattern? and how does the compound components pattern looks like?. Please check out my blog on compound components.

I hope you have used the native HTML Select Component. In the native select component, You can see there are two components : select and option. The both should be used in order to use the native select component.

<select>
 <option>One</option>
 <option>Two</option>
 <option>Three</option>
</select>
Enter fullscreen mode Exit fullscreen mode

The select component is the outer container component. the option are to define options for the select component. The native select component uses the compound components patterns. we are going to use the same pattern for our Select component.

First let's build the select. The Select component is the container components that maintains the state. There are two main states required to build the select component.

  • selectedOption (value of the currently selected option).
  • showDropdown (boolean value to show or hide the dropdown list).
import React, { ReactNode, useState, useRef } from "react";
import useOnClickOutside from "../../hooks/useOnClickOutside";

const Select: React.FC<{
  children: ReactNode | ReactNode[];
  defaultValue?: string;
  placeholder?: string;
}> = ({ children, defaultValue, placeholder }) => {
  const [selectedOption, setSelectedOption] = useState(defaultValue || "");
  const [showDropdown, setShowDropdown] = useState(false);
  const showDropdownHandler = () => setShowDropdown(!showDropdown);
  const selectPlaceholder = placeholder || "Choose an option";

  const clickOutsideHandler = () => setShowDropdown(false);

 // custom hook to detect the click on the outside
  useOnClickOutside(selectContainerRef, clickOutsideHandler);

  const updateSelectedOption = (option: string) => {
    setSelectedOption(option);
    setShowDropdown(false);
  };

  return (
      <div className="select-container" ref={selectContainerRef}>
        <div
          className={showDropdown ? "selected-text active" : "selected-text"}
          onClick={showDropdownHandler}
        >
          {selectedOption.length > 0 ? selectedOption : selectPlaceholder}
        </div>
        <ul
          className={
            showDropdown
              ? "select-options show-dropdown-options"
              : "select-options hide-dropdown-options"
          }
        >
          {children}
        </ul>
      </div>
  );
};

export default Select;
Enter fullscreen mode Exit fullscreen mode

I am using a custom hook called useOnClickOutside. This hook is like a listener that accepts a ref and a callback function. the callback function will be called whenever the click event occurs outside the specified ref. I used this custom hook here to hide the dropdown list whenever the user clicks outside the select component. Now we had finished building the outer component (Select). Next step is to build the Option component.

import React, { ReactNode } from "react";

const Option: React.FC<{
  children: ReactNode | ReactNode[];
  value: string;
}> = ({ children, value }) => {
  return (
    <li className="select-option">
      {children}
    </li>
  );
};

export default Option;
Enter fullscreen mode Exit fullscreen mode

The Option component is now built. Now the difficult part, We need to use the updateSelectedOption function which is present in the select component in the Option component. how are we going to share the function or state between Select and Option Components without passing through props?

Wait What Gif

Chill, React provides a way to share the data without passing through props, This is where the React Contexts comes into play. If you are not familiar with React context, please refer https://reactjs.org/docs/context.html.

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Now Let's write context for select component. We will be sharing two values : selectedOption & updateSelectedOption.

import { createContext, useContext } from "react";

const SelectContext = createContext<{
  selectedOption: string;
  changeSelectedOption: (option: string) => void;
}>({
  selectedOption: "",
  changeSelectedOption: (option: string) => {}
});

const useSelectContext = () => {
  const context = useContext(SelectContext);
  if (!context) {
    throw new Error("Error in creating the context");
  }
  return context;
};

export { useSelectContext, SelectContext };
Enter fullscreen mode Exit fullscreen mode

We had create the select context and useSelectContext is a custom hook for using the context. Now we need to provide the values to the context. We can provide values to the context using the SelectContext.Provider element.

// Select component
 <SelectContext.Provider
      value={{ selectedOption, changeSelectedOption: updateSelectedOption }}
    >
      <div className="select-container" ref={selectContainerRef}>
       ... 
       ...
      </div>
    </SelectContext.Provider>
Enter fullscreen mode Exit fullscreen mode

Now we had provided the values to the Context. Next step is to use the provided values in the Option component. We can make use of useSelectContext to get the values from the context.

import React, { ReactNode } from "react";
import { useSelectContext } from "./selectContext";

const Option: React.FC<{
  children: ReactNode | ReactNode[];
  value: string;
}> = ({ children, value }) => {
  const { changeSelectedOption } = useSelectContext();

  return (
    <li className="select-option" onClick={() => changeSelectedOption(value)}>
      {children}
    </li>
  );
};

export default Option;
Enter fullscreen mode Exit fullscreen mode

We had now connected the Option & Select component. Clicking on any of the option will trigger the changeSelectedOption function which will update the selectedOption state. We can also use selectedOption value in the context in the Option component for highlighting the selected option.

<Select>
     <Option value="one">One</Option>
     <Option value="two">Two</Option>
     <Option value="three">Three</Option>
     <Option value="four">Four</Option>
</Select>
Enter fullscreen mode Exit fullscreen mode

Cheers folks. We had built the Select component which works the same way like the native select component.

Gif description

You can get the complete code and check out the demo in the codesandbox. Happy coding friends.

Top comments (3)

Collapse
 
ashwinkumar0505 profile image
Ashwin kumar

This is just an example to explain the way of using compound components pattern. So It does not handle all the edge cases. If you want to get the selected value. May be you can declare the prop onSelect callback function in Select component. You can call the onSelect whenever the option value changes So that you can get the selected value of the component

Collapse
 
abdulrahmanelheyb profile image
Abdulrahman Elheyb

How can make select multiple ?

Collapse
 
lex_popov profile image
Alexander Popov

How do I use the selected value of this custom selection component?