React compound components in action
Tags: react, javascript, webdev
Published: 2021-04-23 08:35:30

Welcome to my first ever written article. It will be about showing you the usage of compound components through an example of a Modal Box. I used a CSS-in-JS library called [Emotion](https://emotion.sh/docs/introduction) for styling. For the sake of conciseness, the styling codes will be omitted. The goal is to see what compound components bring to the table. The code is available on  [my Github repository](https://github.com/nazifbara/react-modal-box-example).

### Something familiar
Take a glance at this code snippet:
```javascript
<select>
  <option value="value1">option a</option>
  <option value="value2">option b</option>
  <option value="value3">option c</option>
  <option value="value4">option d</option>
</select>
``` 
When an `<option>` is clicked, `<select>` somehow knows about it. This is because a state is implicitly shared between `<select>` and `<option>`. Their awareness of this state allows them, when put together, to do a specific task. I find that we get a nice API out of it. Compound components give the ability to do the same with the help of React Context.  

Context provides a way of sharing values down to the component tree implicitly. It means you don't have to pass values from component to component using props. In certain cases, the use of props can be cumbersome; you can easily end up with a lot of components doing nothing with those values but passing them down to their children. With Context, you get direct access to the needed data. It makes Context a great candidate for implementing compound components.

### They work together
This is the Modal in use:

```javascript
// src/App.js

function App() {
  return (
    <Modal>
      <ModalOpenButton>Open modal</ModalOpenButton>
      <ModalContent title="Modal title here!" imageSrc="./forest.jpg">
        <p>
          Modal Content there!
        </p>
      </ModalContent>
    </Modal>
  );
}
``` 


The `Modal` component is a Context provider that yields a boolean state which says if the Modal is open or not.

```javascript
// src/modal.js

const ModalContext = React.createContext();

function Modal(props) {
  const [isOpen, setIsOpen] = React.useState(false);

  return <ModalContext.Provider value={[isOpen, setIsOpen]} {...props} />;
}
``` 

`ModalOpenButton` consumes the state so that when clicked, the button it returns, set `isOpen` to `true`.


```javascript
// src/modal.js

function ModalOpenButton({ children }) {
  const [, setIsOpen] = React.useContext(ModalContext);
  return <button onClick={() => setIsOpen(true)}>{children}</button>;
}
``` 

Then we have `ModalContent`, also consuming the `ModalContext`, that put contents (its children) in the Modal. It decides to render the Modal Box when `isOpen` is `true` otherwise returns `null`.

```javascript
// src/modal.js

function ModalContent({ children, title, imageSrc }) {
  const [isOpen, setIsOpen] = React.useContext(ModalContext);

  return isOpen ? (
    <Overlay onClick={() => setIsOpen(false)}>
      <div
        css={{...}}
        onClick={(e) => e.stopPropagation()}
      >
        <div css={{...}}>
          <h2 css={{..}}>
            {title}
          </h2>
          <ModalCloseButton />
        </div>
        <div css={{...}}>{children}</div>
      </div>
    </Overlay>
  ) : null;
}
```
Once the Modal is open there are two ways of closing it: clicking the `Overlay` or the `ModalCloseButton`. `Overlay` is a styled component and `ModalCloseButton` another `ModalContext` consumer. They both set `isOpen` to `false` when clicked.

```javascript
// src/modal.js

const Overlay = styled.div({...});

function ModalCloseButton() {
  const [, setIsOpen] = React.useContext(ModalContext);

  return (
    <button
      onClick={() => setIsOpen(false)}
      css={{...}}
    >
      <img alt="" src={timeSVG} />
    </button>
  );
}
``` 

### Conclusion
Here is the list of our compound components:

- `Modal`
- `ModalContent`
- `ModalOpenButton`
- `ModalCloseButton`

They are all sync around a common state each taking action to bring specific functionality. Put them apart and they will not be as useful. Thanks for reading!