How to use Popper with Svelte, in a Sveltish way, with Actions!
Tags: svelte, javascript, webdev, tutorial
Published: 2020-11-10 18:15:05

#What is Popper?
[Popper](https://popper.js.org/) identifies itself as a `TOOLTIP & POPOVER POSITIONING ENGINE`. It basically helps your popovers and tooltips position properly. Popper is awesome and used by millions and giants (according to their website) such as Microsoft, Atlassian, GitLab, etc. And I use it at work and while working on my side projects.

##Okay, but with Svelte we can use any tools or libraries without an extra work or binding
Yes but it doesn't mean that we can't improve our codebase.

##Yep, you're right, so how to use it?
First, I want to show you how you can use it without `Sveltish` way. Let's create a new Svelte project with:
```sh
npx degit sveltejs/template my-svelte-project
cd sveltishpopper
npm i
```
Then install Popper
```sh
npm i @popperjs/core
```

Then open `src/App.svelte` with your favorite editor, and delete everything.

Create a file named `Modal.svelte`, then paste following:
```js
<style>
  #modal {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 40vh;
    width: 20vw;
    flex-direction: column;
    background-color: wheat;
    border-radius: 1em;
  }
  #modal > img {
    margin-top: 3em;
  }
</style>
<div id="modal">
  Hello Popper!
  <img src="https://popper.js.org/static/popper-logo-394b4ea5914aad7fc580f418ed0cfb17.svg" alt="Popper logo">
</div>
```
It's just a basic modal with a background color.

Now open `App.svelte` and paste
```js
<script>
	import { onMount } from 'svelte';
	import { createPopper } from '@popperjs/core/dist/esm';
	import Modal from './Modal.svelte';
	let popButton, modal, isModalActive = false;
	const toggleModal = () => {
		isModalActive = !isModalActive;
	};
	onMount(() => {
		createPopper(popButton, modal);
	});
</script>

<button bind:this={popButton} on:click={toggleModal}>
	Pop it
</button>
{#if isModalActive}
	<Modal bind:this={modal} />
{/if}
```
In the code above, nothing really challenging goes on
And congratz! You got yourself a functioning modal with awesome positioning.
![bad popper](https://dev-to-uploads.s3.amazonaws.com/i/b4f0rsfx1dg11h5fn2uf.gif)
This implementation is really simple but everytime you want to use a modal with popper, you need to repeat this implementation. How can we improve?

##Action!
![Action gif](https://media1.tenor.com/images/f9eae32ae9741644afccb8c1aa68b06b/tenor.gif?itemid=16202503)

Now create a file named, actually it doesn't really matter because this isn't really a project but let's call it, `PopperAction.js`.

And paste following:
```js
import { createPopper } from '@popperjs/core/dist/esm';

export function popover(node, { component, ...props }) {
  const button = node;
  let popperInstance, componentInstance, renderedComponent, isActive = false;
  const id = 'modal';
  
  const toggle = e => {
    e.stopPropagation()
    isActive ? hide() : show();
  };

  button.addEventListener('click', toggle);

  const detectClickOutside = event => {
    if (renderedComponent && !renderedComponent.contains(event.target) && isActive) {
      hide();
    }
  };

  const show = () => {
    componentInstance = new component({
      target: document.body,
      props
    });
    isActive = true;
    renderedComponent = document.querySelector(`#${id}`);

    popperInstance = createPopper(button, renderedComponent, {
      modifiers: [
        {
          name: 'offset',
          options: {
            offset: [0, 8]
          }
        }
      ]
    });

    document.addEventListener('click', detectClickOutside);
  };

  const hide = () => {
    renderedComponent = document.querySelector(`#${id}`);
    isActive = false;
    if (popperInstance) {
      popperInstance.destroy();
      popperInstance = null;
    }
    componentInstance.$destroy();
    document.removeEventListener('click', detectClickOutside);
  }

  return {
    destroy() {
      button.removeEventListener('click', toggle);
      document.removeEventListener('click', detectClickOutside);
    }
  }
}
```
##Okay, what the heck??
![what the ...](https://media1.tenor.com/images/a4ccb1ae7720c8bdac17ecd78fe3ed43/tenor.gif?itemid=13712192)
This implementation is more confusing but let me explain what's happening down there. (You can skip if you understood what it does.)

So, we are defining a function that takes, a node and some props. We add a click event listener to the node (in this case its a button) and we bind it to a toggle function, which toggles the modal (eh?).

Show function is creating a popper instance every time we click to the button, and hide is hiding the modal and destroying the popper instance. You can figure out the optimizations yourselves, I'm in a rush!

As a bonus I ended up adding a click outside handler which detects clicks that are outside of the modal.

We return an object from the action.
```js
  return {
    destroy() {
      button.removeEventListener('click', toggle);
      document.removeEventListener('click', detectClickOutside);
    }
  }
```
It has a special method named `destroy`, it's duty is to clean up effects ( :) ).

## Hmm, seems legit, but how to use it?
![seems legit](https://media.tenor.com/images/743666c385590f747a6e5596f73d3b6c/tenor.gif)

That's the awesome part. Get ready to shocked in 3, 2, 1...
![Carbon last](https://dev-to-uploads.s3.amazonaws.com/i/lnt8n31erguycgnncoto.png)
```js
<script>
	import { popover } from './PopperAction.js';
	import Modal from './Modal.svelte';	
</script>

<button use:popover={{ component: Modal }}>
	Pop it
</button>
```
Look how beautiful it is. 😢

Okay this is it. Have a nice day.

<sub><sup>PS, checkout official <a href="https://github.com/popperjs/svelte-popper">svelte package of popper</a></sup></sub>