[PART 17][Frontend] Creating a Twitter clone with GraphQL, Typescript, and React ( Navbar )
Tags: react, webdev, graphql, challenge
Published: 2021-01-24 20:20:49

Hi everyone ;).

As a reminder, I'm doing this [Tweeter challenge](https://devchallenges.io/challenges/rleoQc34THclWx1cFFKH)

[Github repository ( Backend )](https://github.com/ips-coding-challenge/challenge_twitter)

[Github repository ( Frontend )](https://github.com/ips-coding-challenge/challenge_twitter_front)

[Db diagram](https://dbdiagram.io/d/5f75c0243a78976d7b75f7be)



## Navbar

According to the design, there are two different navbar. One for the desktop and another one for mobile with a fixed menu in the bottom of the screen. I also have a condition to render the menu only if we have a connected user.

### Desktop

Here is what my Navbar component looks like:

*src/components/navbar/Navbar.tsx*

```tsx
import { Link } from 'react-router-dom'
import { useRecoilValue } from 'recoil'
import logo from '../../assets/tweeter.svg'
import { userState } from '../../state/userState'
import Menu from './Menu'
import UserMenu from './UserMenu'

const Navbar = () => {
  const user = useRecoilValue(userState)

  return (
    <div className="h-navbar border-b border-gray2 flex-none">
      <div className="w-full px-4 h-full flex items-center justify-between">
        <Link to="/">
          <img src={logo} alt="Logo Tweeter" />
        </Link>

        {user && (
          <>
            {/* Menu */}
            <Menu />
            {/* User menu */}
            <UserMenu />
          </>
        )}
      </div>
    </div>
  )
}

export default Navbar

```

I get the connected user from my recoil state.

The only "interesting" thing here is the UserMenu which has a custom drop-down menu.

*src/components/navbar/UserMenu.tsx*

```tsx
import React, { useRef, useState } from 'react'
import { MdArrowDropDown } from 'react-icons/md'
import { useRecoilValue } from 'recoil'
import { useClickOutside } from '../../hooks/useClickOutside'
import { userState } from '../../state/userState'
import Avatar from '../Avatar'
import UserDropdown from './UserDropdown'

const UserMenu = () => {
  const [showDropdown, setShowDropdown] = useState(false)
  const user = useRecoilValue(userState)
  const menuRef = useRef(null)
  const dropdownRef = useRef(null)
  useClickOutside(dropdownRef, menuRef, () => {
    setShowDropdown(false)
  })
  return (
    <div
      ref={menuRef}
      className="flex items-center justify-center relative"
      onClick={() => setShowDropdown((old) => !old)}
    >
      <Avatar display_name={user?.display_name!} className="mr-3" />
      <div className="hidden cursor-pointer md:flex items-center">
        <div className="mr-4">{user?.display_name}</div>
        <MdArrowDropDown className="text-xl" />
      </div>
      <UserDropdown ref={dropdownRef} show={showDropdown} />
    </div>
  )
}

export default UserMenu

```

I created a custom hook to listen when a user clicks outside the drop-down.

*src/hooks/useClickOutside.ts*

```typescript
import { exception } from 'console'
import { useEffect } from 'react'

export const useClickOutside = (
  ref: any,
  excludeRef: any,
  callback: Function
) => {
  useEffect(() => {
    const handleClickOutside = (event: any) => {
      if (ref.current && !ref.current.contains(event.target)) {
        if (excludeRef.current && !excludeRef.current.contains(event.target)) {
          callback()
        }
      }
    }
    // Bind the event listener
    document.addEventListener('mousedown', handleClickOutside)
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener('mousedown', handleClickOutside)
    }
  }, [ref])
}

```

The particularity here is the **excludeRef** property. I added it to allow to toggle the dropdown when clicking on the trigger ( the user's name ).

*src/components/navbar/UserDropdown.tsx*

```tsx
import React, { forwardRef, useRef } from 'react'
import {
  MdAccountCircle,
  MdExitToApp,
  MdPeople,
  MdSettings,
} from 'react-icons/md'
import { Link } from 'react-router-dom'
import { useClickOutside } from '../../hooks/useClickOutside'
import UserDropdownLink from './UserDropdownLink'

type UserDropdownProps = {
  show: boolean
}

const UserDropdown = forwardRef(({ show }: UserDropdownProps, ref: any) => {
  return show ? (
    <div
      ref={ref}
      className="absolute top-0 right-0 mt-16 w-menuDropdown bg-white px-4 py-2 rounded-lg border border-gray6"
    >
      <div className="flex flex-col">
        <UserDropdownLink icon={<MdAccountCircle />} text="Profile" to="/" />
        <UserDropdownLink icon={<MdSettings />} text="Settings" to="/" />
      </div>
      <hr />
      <div>
        <UserDropdownLink
          icon={<MdExitToApp />}
          text="Logout"
          to="/"
          className="text-red-500"
        />
      </div>
    </div>
  ) : null
})

export default UserDropdown

```

![Navbar Desktop](https://dev-to-uploads.s3.amazonaws.com/i/qgussccgzru16o8c6b51.png)

That's it for the desktop part. Let's take a look at the mobile version.

### Mobile

For the mobile version, I have modified the Layout component a little bit.

*src/components/Layout.tsx*

```tsx
import React from 'react'
import { useRecoilValue } from 'recoil'
import { userState } from '../state/userState'
import MenuMobile from './navbar/MenuMobile'
import Navbar from './navbar/Navbar'

type LayoutProps = {
  children: React.ReactNode
}

const Layout = ({ children }: LayoutProps) => {
  const user = useRecoilValue(userState)

  return (
    <div className="flex flex-col h-screen overflow-hidden md:h-full md:overflow-auto">
      <Navbar />
      <div className="w-full h-full overflow-y-auto md:overflow-y-visible">
        {children}
      </div>

      {/* Menu For Mobile */}
      {user && <MenuMobile />}
    </div>
  )
}

export default Layout

```

*src/components/navbar/MenuMobile.tsx*

```tsx
import React from 'react'
import { MdBookmarkBorder, MdExplore, MdHome } from 'react-icons/md'

const MenuMobile = () => {
  return (
    <div className="md:hidden w-full h-16 bg-white z-10 flex flex-none items-center justify-around">
      <MdHome className="text-xl" />
      <MdExplore className="text-xl" />
      <MdBookmarkBorder className="text-xl" />
    </div>
  )
}

export default MenuMobile

```

![Navbar Mobile](https://dev-to-uploads.s3.amazonaws.com/i/zy1nd1mo680nyg7izaew.png)

There is still a lot of work to be done, but I'm moving forward ;). 

We will have a problem with the **height:100vh** even if it seems to work. I already had this issue on the [Shoppingify Challenge](https://dev.to/ipscodingchallenge/continuing-my-learning-path-i-did-a-trello-clone-55el). You can look [here](https://github.com/Shoppingify/frontend/blob/e213d199e917113a38a91cdb3daf771e650f1546/src/App.tsx#L79-L92) for a solution. This solution isn't perfect ( as it will re-render on every resize event ) and that's why I will not implement it right away ;).



That's all for today.



Bye and take care ;).