Create custom select component in React(Compound components pattern + Typescript)
Tags: react, compoundcomponents, select, typescript
Published: 2021-08-16 05:24:51

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](https://hashnode.com/post/react-compound-components-hooks-typescript-ckpf00izg033kkms134jo828m). 

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.

```jsx
<select>
 <option>One</option>
 <option>Two</option>
 <option>Three</option>
</select>
``` 

 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).

```jsx
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;
``` 
I am using a custom hook called [useOnClickOutside](https://hashnode.com/post/useonclickoutside-custom-hook-to-detect-the-mouse-click-on-outside-typescript-ckrejmy3h0k5r91s18iu42t28). 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.

```jsx
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;
``` 
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](https://media.giphy.com/media/zrmTqopWm4W5cPg8Ah/giphy.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.

```jsx
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 };
``` 
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.

```jsx
// Select component
 <SelectContext.Provider
      value={{ selectedOption, changeSelectedOption: updateSelectedOption }}
    >
      <div className="select-container" ref={selectContainerRef}>
       ... 
       ...
      </div>
    </SelectContext.Provider>
``` 
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.

```jsx
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;
``` 
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.
```jsx
<Select>
     <Option value="one">One</Option>
     <Option value="two">Two</Option>
     <Option value="three">Three</Option>
     <Option value="four">Four</Option>
</Select>
```
 Cheers folks. We had built the Select component which works the same way like the native select component.

![Gif description](https://media.giphy.com/media/8Iv5lqKwKsZ2g/giphy.gif)

You can get the complete code and check out the demo in the [codesandbox](https://codesandbox.io/s/heuristic-nightingale-mhurf?file=/src/App.tsx:241-466). Happy coding friends.