Creating a React Modal with React custom Hooks and Typescript
Tags: react, typescript, javascript, designpatterns
Published: 2022-08-07 14:54:01

<p>Hello Guys,</p>
<p>React hooks are very useful features of React and with this comes custom React hooks, these custom React hooks have endless possibilities and can easily replace container Logic in Container/Presentational Model.</p>
<p>Read more about that here <a target="_blank" href="https://www.patterns.dev/posts/#design-patterns">Patter.dev</a></p>
<h1 id="heading-creating-the-modal-from-scratch">Creating the Modal from Scratch</h1>
<h2 id="heading-creating-the-presentational-part-of-modal">Creating the Presentational Part of Modal</h2>
<p>Let's create the presentational part of the  Modal in this I will be quick and just show you the code </p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">React</span>, { <span class="hljs-title">ReactNode</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;


<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ModalType</span> </span>{
  children?: ReactNode;
  isOpen: boolean;
  toggle: () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> void;
}

export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Modal</span>(<span class="hljs-params">props: ModalType</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">&gt;</span>
        <span class="hljs-operator">&lt;</span>div className<span class="hljs-operator">=</span><span class="hljs-string">"modal-overlay"</span> <span class="hljs-operator">&gt;</span>
          <span class="hljs-operator">&lt;</span>div  className<span class="hljs-operator">=</span><span class="hljs-string">"modal-box"</span><span class="hljs-operator">&gt;</span>
            {props.children}
          <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
        <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
  );
}
</code></pre><p>let me explain this to you, So here we have two div an <code>overlay-div</code> which have full width and height, and a <code>modal-box div</code>. </p>
<p>So we are trying to achieve something like below where if the user clicks anywhere else than the <code>modal-box div</code> the modal will get closed. And all the modal content will be inside the white part of the modal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659644607856/IjCMklBHB.png" alt="image.png" /></p>
<p>let's quickly set up the CSS for our modal divs.</p>
<pre><code>
<span class="hljs-selector-class">.modal-overlay</span> {
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">9999</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100vw</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>);
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-class">.modal-box</span> {
  <span class="hljs-attribute">display</span>: block;
  <span class="hljs-attribute">background</span>: white;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">70%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">70%</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">1rem</span>;
}
</code></pre><p>once this is done we need will get to our logic part where we will only render the Modal if our state is true. </p>
<h2 id="heading-why-custom-hook">Why custom Hook?</h2>
<p>Here I used a custom hook so that I don't have to write the same logic again and again when trying to use modal and add the same function in all the files.</p>
<h2 id="heading-adding-logic-to-the-hooks">Adding logic to the Hooks</h2>
<p>let's quickly make a new file <code>useModal.tsx</code> in our <code>hooks</code> folder this will be a pure function and only contain the logic for now.</p>
<pre><code><span class="hljs-comment">// useModal.tsx</span>

<span class="hljs-keyword">import</span> { <span class="hljs-title">useState</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;

export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useModal</span>(<span class="hljs-params"></span>) </span>{
  const [isOpen, setisOpen] <span class="hljs-operator">=</span> useState(<span class="hljs-literal">false</span>);

  const toggle <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
    setisOpen(<span class="hljs-operator">!</span>isOpen);
  };

  <span class="hljs-keyword">return</span> {
    isOpen,
    toggle
  };
}
</code></pre><p>In this, we have a state <code>isOpen</code> to show or hide the modal, and then there is a function that actually toggles the visibility of the Modal.</p>
<p>now let's use this code in our <code>App.tsx</code> and implement a Modal that gets triggered on click</p>
<h2 id="heading-combining-the-logic-and-presentational-part-hooks-modal-component">Combining the logic and Presentational Part ( hooks + Modal component)</h2>
<h3 id="heading-calling-the-modal-component-and-usemodal">Calling the Modal component and useModal</h3>
<p>let's import out the Modal component and <code>useModal</code> hook in <code>App.tsx</code> </p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">Modal</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"./component/modal"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title">useModal</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../src/hooks/useModal"</span>;
</code></pre><p>let's add the other logic and pass <code>isOpen</code> and <code>toggle</code> to the <code>Modal</code> component.</p>
<p>our <code>App.tsx</code> will look something like below</p>
<pre><code>export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  const { isOpen, toggle } <span class="hljs-operator">=</span> useModal();
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&lt;</span>div className<span class="hljs-operator">=</span><span class="hljs-string">"App"</span><span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>h1<span class="hljs-operator">&gt;</span>Hello CodeSandbox<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>h1<span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>h2<span class="hljs-operator">&gt;</span>Start editing to see some magic happen<span class="hljs-operator">!</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>h2<span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>button onClick<span class="hljs-operator">=</span>{toggle}<span class="hljs-operator">&gt;</span>Open Modal <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>button<span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>Modal isOpen<span class="hljs-operator">=</span>{isOpen} toggle<span class="hljs-operator">=</span>{toggle}<span class="hljs-operator">&gt;</span><span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>Modal<span class="hljs-operator">&gt;</span>
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
  );
}
</code></pre><h3 id="heading-adding-logic-to-modal-component">Adding Logic to Modal component</h3>
<p>Well at this point we are almost done all we need is to add some basic conditional rendering logic to our Modal component and let's see what we have.</p>
<pre><code><span class="hljs-comment">// Modal.tsx</span>

<span class="hljs-keyword">import</span> <span class="hljs-title">React</span>, { <span class="hljs-title">ReactNode</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ModalType</span> </span>{
  children?: ReactNode;
  isOpen: boolean;
  toggle: () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> void;
}

export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Modal</span>(<span class="hljs-params">props: ModalType</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">&gt;</span>
      {props.isOpen <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> (
        <span class="hljs-operator">&lt;</span>div className<span class="hljs-operator">=</span><span class="hljs-string">"modal-overlay"</span> onClick<span class="hljs-operator">=</span>{props.toggle}<span class="hljs-operator">&gt;</span>
          <span class="hljs-operator">&lt;</span>div  className<span class="hljs-operator">=</span><span class="hljs-string">"modal-box"</span><span class="hljs-operator">&gt;</span>
            {props.children}
          <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
        <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
      )}
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
  );
}
</code></pre><p>we added the onClick event and passed the <code>toggle</code> function to it so that if we click anywhere else on the outside of the modal, the modal get's close.</p>
<p>Also, we rendered our component such that if <code>isOpen = false</code> it will return nothing.</p>
<p>At this point we are done let's see how it looks now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659645825205/PyuaqONbo.gif" alt="CPT2208050212-632x542.gif" /></p>
<p>Now it's working fine .......... Well Almost actually.</p>
<p>if you noticed if we click on the inner div even then the whole modal is getting close which is not something we can do as this can lead to several issues. </p>
<p>This is happening due to the phenomenon called Event Bubbling in Javascript where any event is propagated through the parent divs.</p>
<h1 id="heading-solving-bugs-and-handling-event-propagation">Solving bugs and handling Event Propagation.</h1>
<p>In React this can be easily solved by the using <code>event.stopPropagation()</code> method which is already provided by React.</p>
<p>We will just call this method when the user clicks on the Inner div.</p>
<p>let's see how our code looks now</p>
<pre><code><span class="hljs-comment">// Modal.tsx</span>

<span class="hljs-keyword">import</span> <span class="hljs-title">React</span>, { <span class="hljs-title">ReactNode</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">ModalType</span> </span>{
  children?: ReactNode;
  isOpen: boolean;
  toggle: () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> void;
}

export default <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Modal</span>(<span class="hljs-params">props: ModalType</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">&gt;</span>
      {props.isOpen <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> (
        <span class="hljs-operator">&lt;</span>div className<span class="hljs-operator">=</span><span class="hljs-string">"modal-overlay"</span> onClick<span class="hljs-operator">=</span>{props.toggle}<span class="hljs-operator">&gt;</span>
          <span class="hljs-operator">&lt;</span>div onClick<span class="hljs-operator">=</span>{(e) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> e.stopPropagation()} className<span class="hljs-operator">=</span><span class="hljs-string">"modal-box"</span><span class="hljs-operator">&gt;</span>
            {props.children}
          <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
        <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">&gt;</span>
      )}
    <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
  );
}
</code></pre><p>Yaay !!!!!!!!!! now the above problem is solved</p>
<h1 id="heading-try-this-on-sandbox">Try this on Sandbox?</h1>
<iframe src="https://codesandbox.io/embed/modal-using-react-hooks-2jn08i?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:100%;height:500px;border:0;border-radius:4px;overflow:hidden" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>


<p>Hope you like testing it try adding more features to it.</p>
<h1 id="heading-tldr">TL;DR</h1>
<ul>
<li>We learned about container/presentational design patter</li>
<li>We learned how to create our own custom hooks and separate all the logic parts from the UI.</li>
<li>We learned how the Modal component works and to create reusable React component</li>
<li>We learned what is event propagation and how to stop it.</li>
</ul>
<p>if you like this post let's get connected on Twitter:</p>
<p> <a target="_blank" href="https://twitter.com/theysaymaurya">Follow me here</a></p>
</br><p style="color:#808080;">This blog is posted using <a target="_blank" href="https://blogtiple.com">Blogtiple</a></p>