DEV Community

Cover image for [PART 17][Frontend] Creating a Twitter clone with GraphQL, Typescript, and React ( Navbar )

Posted on

[PART 17][Frontend] Creating a Twitter clone with GraphQL, Typescript, and React ( Navbar )

Hi everyone ;).

As a reminder, I'm doing this Tweeter challenge

Github repository ( Backend )

Github repository ( Frontend )

Db diagram


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.


Here is what my Navbar component looks like:


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" />

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

export default Navbar

Enter fullscreen mode Exit fullscreen mode

I get the connected user from my recoil state.

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


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, () => {
  return (
      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" />
      <UserDropdown ref={dropdownRef} show={showDropdown} />

export default UserMenu

Enter fullscreen mode Exit fullscreen mode

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


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( {
        if (excludeRef.current && !excludeRef.current.contains( {
    // Bind the event listener
    document.addEventListener('mousedown', handleClickOutside)
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener('mousedown', handleClickOutside)
  }, [ref])

Enter fullscreen mode Exit fullscreen mode

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


import React, { forwardRef, useRef } from 'react'
import {
} 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 ? (
      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="/" />
      <hr />
          icon={<MdExitToApp />}
  ) : null

export default UserDropdown

Enter fullscreen mode Exit fullscreen mode

Navbar Desktop

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


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


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">

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

export default Layout

Enter fullscreen mode Exit fullscreen mode


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" />

export default MenuMobile

Enter fullscreen mode Exit fullscreen mode

Navbar Mobile

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. You can look here 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 ;).

Top comments (0)