Open and close mobile navbar menu using styled components - javascript

I'm following a guide on how to build a simple website using Styled Components.
When the website is in mobile view the hamburger menu is stuck open and will not close when clicking the hamburger symbol or any of the nav menu items. The hamburger symbol does toggle to the FaTimes symbol, however.
All solutions I found so far don't work or require bootstrap, which I'm unfamiliar with. Can someone please explain to me the error in my code?
Navbar
import React, {useState, useEffect} from 'react';
import { FaBars, FaTimes } from 'react-icons/fa';
import { Nav, NavbarContainer, NavLogo, NavIcon, MobileIcon, NavItem, NavMenu, NavLinks,
NavItemBtn, NavBtnLink } from './Navbar.elements';
import {Button } from '../globalstyles';
import osun from '../images/Osun.png';
import { IconContext} from 'react-icons/lib';
import Home from './pages/Home';
const Navbar = () => {
const [click, setClick, isOpen, setIsOpen] = useState(false);
const [button, setButton] = useState(true);
const toggle = () => setIsOpen(!isOpen);
const hide = () => setIsOpen(false);
const handleClick = () => setClick(!click);
// const toggleShow = () => {
// this.setState({show: !this.state.show})
// }
//const toggleShow = () => setClick(!show)
// const toggleShow = () => {
// if (window.innerwidth <= 960){
// showMenu(false)
// } else {
// showMenu(true)
// }
// }
const showButton = () => {
if (window.innerWidth <=960){
setButton(false)
} else {
setButton(true)
}
};
const closeMobileMenu = () => setClick(false);
useEffect(() => {
showButton();
//toggleShow()
}, []);
window.addEventListener('resize', showButton);
//window.addEventListener('resize', toggleShow);
return (
<>
<IconContext.Provider value={{color: "#fff"}}>
<Nav>
<NavbarContainer>
<NavLogo to ="/">
<NavIcon />
Osun Swap
</NavLogo>
<MobileIcon onClick={handleClick} onBlur={hide} >
{click ? < FaTimes/> : <FaBars/>}
</MobileIcon>
<NavMenu onClick={handleClick} click ={click}>
<NavItem>
<NavLinks to = '/' onClick={closeMobileMenu}>
Home
</NavLinks>
</NavItem>
<NavItem>
<NavLinks to = '/services' onClick={closeMobileMenu}>
Services
</NavLinks>
</NavItem>
<NavItem>
<NavLinks to = '/AboutUs' onClick={closeMobileMenu}>
About us
</NavLinks>
</NavItem>
<NavItemBtn>
{button ? (
<NavBtnLink to ='/sign-up'>
<Button primary> Connect Wallet </Button>
</NavBtnLink>
) : (
<NavBtnLink to='/sign-up'>
<Button onClick={closeMobileMenu} fontBig primary>
Connect Wallet
</Button>
</NavBtnLink>
)}
</NavItemBtn>
</NavMenu>
</NavbarContainer>
</Nav>
</IconContext.Provider>
</>
)
}
export default Navbar;
Navbar.elements
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import osun from '../images/Osun.png';
import { Container } from '../globalstyles';
export const Nav = styled.nav`
background : #101522;//#282c34;//rgb(35, 31,32);//; //rgb(10, 10, 10);
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
position: sticky;
top:0;
z-index:999;
`;
export const NavbarContainer = styled(Container)`
display: flex;
justify-content:space-between;
height: 80px;
${Container}
`;
export const NavLogo = styled(Link)`
color: #ffff;
justify-self: flex-start;
cursor: pointer;
font-size: 2rem;
display: flex;
align-items: center;
`;
export const NavIcon = styled.div`
background-image: url(${osun});
margin-right: 0.5rem;
`;
export const MobileIcon = styled.div`
display: none;
#media screen and (max-width: 960px){
display:block;
position: absolute;
top: 0;
right:0 ;
transform: translate(-100%, 60%);
font-size: 1.8rem;
cursor: pointer;
}
`;
export const NavMenu = styled.ul`
display: flex;
align-items: center;
list-style: none;
//text-decoration: none;
text-align: center;
/* display: flex;
flex-direction: column;
width: 100px;
height: 500px;
top: 80px;
left: -100%;
opacity:1;
transition: all 0.5s ease; */
#media screen and (max-width: 960px) {
display: flex;
flex-direction: column;
width: 100%;
//list-style: none;
height: 90vh;
top: 80px;
//right: -100%;
right: ${({click}) => (click ? 0 : '-100%')};
opacity:1;
//transform: ${({open}) => open ? 'translateX(0)' : 'translateX(100%)'};
transition: all 0.5s ease;
background: #101522; //rgb(35, 31,32);//
}
`;
export const NavItem = styled.li`
height: 80px;
border-bottom: 2px solid transparent;
#media screen and (max-width: 960px){
//visibility: hidden;
// display: block;
position: relative;
width: 100%;
//top:0;
//right:0;
//transform: translate(-100%, 60%);
//font-size:1.8rem;
//cursor: pointer;
}
&:hover {
border-bottom: 2px solid #4b59f7;
}
`;
export const NavLinks = styled(Link)`
color: #fff;
display:flex;
align-items: center;
text-decoration: none;
padding:0.5rem 1rem;
height: 100%;
#media screen and (max-width: 960px){
text-align: center;
padding: 2rem;
width: 100%;
display: table;
&:hover {
color:#4b59f7;
transition: all 0.3s ease;
}
}
`;
export const NavItemBtn = styled.li`
#media screen and (max-width: 960){
display:flex;
justify-content: center;
align-items: center;
width: 100%;
height: 120px
}
`;
export const NavBtnLink= styled(Link)`
display: flex;
justify-content:center;
align-items: center;
text-decoration: none;
padding: 8px 16px;
height: 100%;
width: 100%;
border: none;
outline: none;
`;

I tried to reproduce your issue and ran into errors with your code example, which may be the cause of your issue. In particular:
const [click, setClick, isOpen, setIsOpen] = useState(false);
is not a valid setup for useState. Try splitting this into two separate useState calls like:
const [click, setClick] = useState(false);
const [isOpen, setIsOpen] = useState(false);
As you had it in the original example, my IDE immediately flagged that line, and also informed me that isOpen was not initialized to anything, which threw major errors when I tried to click the button / blur it.

Related

Issues with getting the css class to change based off uid from firestore React

I hope you all are doing well. I was following this fireship io tutorial in building a chat app in react. I updated how I got the data with hooks instead of how it was done in the video.
The issue is, I have it working where I can send and receive messages, but I can't apply the appropriate CSS style depending on which user sent a message. The odd part is, I'm able to console log the text and the uid as props.
The focus is on const ChatMessage where, depending on the uid of the current user, it will change the CSS styling to mimic that of imessage. Here's an example.
Final Product
I have it set up where I run npm from vs code and I access the app from two of my google accounts. One from firefox, the other from chrome.
I'm really trying to get a hang of firebase react with hooks, anything helps.
Cheers,
Tutorial Source Code: https://github.com/fireship-io/react-firebase-chat
App.js
import "./App.css";
import "firebase/firestore";
import "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
import { db } from "./firebase";
import {
collection,
onSnapshot,
addDoc,
serverTimestamp,
query,
orderBy,
limit,
} from "firebase/firestore";
import { useEffect, useState, useRef } from "react";
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
const provider = new GoogleAuthProvider();
const auth = getAuth();
const userID = "";
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<header>
<h1>SUp# Ch#tZ🫡</h1>
<SignOut />
</header>
<section>{user ? <ChatRoom /> : <SignIn />}</section>
</div>
);
}
const ChatRoom = () => {
const [messages, setMessages] = useState([]);
const [formValue, setFormValue] = useState("");
const messagesRef = collection(db, "messages");
const recentMessage = useRef();
const queryAtts = query(messagesRef, orderBy("createdAt"), limit(25));
//Send Messages
const sendMessage = async (e) => {
e.preventDefault();
const { uid, photoURL } = auth.currentUser;
await addDoc(collection(db, "messages"), {
text: formValue,
createdAt: serverTimestamp(),
uid,
photoURL,
});
setFormValue("");
recentMessage.current.scrollIntoView({ behavior: "smooth" });
};
//Get Messages from Firestore
useEffect(() => {
//console.log(messagesRef);
onSnapshot(queryAtts, (snapshot) => {
setMessages(
snapshot.docs.map((doc) => {
return { id: doc.id, viewing: false, ...doc.data() };
})
);
});
}, []);
return (
<>
<main>
{messages &&
messages.map((msg, i) => (
<>
{console.log("Pre-Chat Msg uid: ", msg.uid)}
<ChatMessage
msg={msg.text}
uid={msg.uid}
photoURL={auth.currentUser.photoURL}
/>
</>
))}
<div ref={recentMessage}></div>
</main>
<form onSubmit={sendMessage}>
<input
value={formValue}
onChange={(e) => setFormValue(e.target.value)}
/>
<button type="submit" disabled={!formValue}>
😶‍🌫️
</button>
</form>
</>
);
};
const ChatMessage = (props) => {
//console.log("uid in chat msg: ", props.uid);
console.log("chat message photo: ", props.photoURL);
const messageClass = props.uid === auth.currentUser ? "sent" : "recieved";
console.log("useAuthStateHook uid: ", props.uid);
return (
<div className={`message ${messageClass}`}>
<img
src={
props.photoURL || "https://xsgames.co/randomusers/avatar.php?g=pixel"
}
alt={"broken lol"}
/>
<p key={props.uid}>{props.msg}</p>
</div>
);
};
const SignIn = () => {
const useSignInWithGoogle = () => {
signInWithPopup(auth, provider);
};
return (
<>
<button onClick={useSignInWithGoogle}>SignIn W/ Google</button>
<p>
Do not violate the community guidelines or you will be banned for life!
</p>
</>
);
};
const SignOut = () => {
return (
auth.currentUser && (
<button className="sign-out" onClick={() => auth.signOut()}>
Sign Out
</button>
)
);
};
export default App;
App.css
body {
background-color: #282c34;
}
.App {
text-align: center;
max-width: 728px;
margin: 0 auto;
}
.App header {
background-color: #181717;
height: 10vh;
min-height: 50px;
color: white;
position: fixed;
width: 100%;
max-width: 728px;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 99;
padding: 10px;
box-sizing: border-box;
}
.App section {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100vh;
background-color: rgb(40, 37, 53);
}
main {
padding: 10px;
height: 80vh;
margin: 10vh 0 10vh;
overflow-y: scroll;
display: flex;
flex-direction: column;
}
main::-webkit-scrollbar {
width: 0.25rem;
}
main::-webkit-scrollbar-track {
background: #1e1e24;
}
main::-webkit-scrollbar-thumb {
background: #6649b8;
}
form {
height: 10vh;
position: fixed;
bottom: 0;
background-color: rgb(24, 23, 23);
width: 100%;
max-width: 728px;
display: flex;
font-size: 1.5rem;
}
form button {
width: 20%;
background-color: rgb(56, 56, 143);
}
input {
line-height: 1.5;
width: 100%;
font-size: 1.5rem;
background: rgb(58, 58, 58);
color: white;
outline: none;
border: none;
padding: 0 10px;
}
button {
background-color: #282c34; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
font-size: 1.25rem;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.sign-in {
color: #282c34;
background: white;
max-width: 400px;
margin: 0 auto;
}
ul, li {
text-align: left;
list-style: none;
}
p {
max-width: 500px;
margin-bottom: 12px;
line-height: 24px;
padding: 10px 20px;
border-radius: 25px;
position: relative;
color: white;
text-align: center;
}
.message {
display: flex;
align-items: center;
}
.sent {
flex-direction: row-reverse;
}
.sent p {
color: white;
background: #0b93f6;
align-self: flex-end;
}
.received p {
background: #e5e5ea;
color: black;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 2px 5px;
}
I've tried maneuvering where the auth.currentUser was called. I also reformatted my functions from the function to const format, but I don't believe that makes much of a difference.
It seems that in your ChatMessage component, the messageClass will get a value based on if props.uid equals to uid of the current user.
If this is the goal, you can set it like:
const messageClass = props.uid === auth.currentUser?.uid ? "sent" : "recieved";
Instead of:
const messageClass = props.uid === auth.currentUser ? "sent" : "recieved";
Because auth.currentUser is the object that has some user data properties such as uid.

How to display data from clicked container to other con

Can someone help me to display specific song while clicking on that in Your Playlist container on the left??
I am trying to list data on the left in Your Playlist container When I click on one of the music it should show it in Your Playlist container. It has to save it to browser history as well and it has to remove it from Search because it is already gonna be in Your Playlist container. I will deploy it later to Firebase but now I need help.
It should be added to the left while clicking on one of the listed songs after a search.
Please support me on that.
I am adding my codes here as well for my project:
I have App.js
import "./App.css";
import MySongs from "./MySongs.js";
import Search from "./Search.jsx";
function App() {
return (
<div className="App">
<div className="body">
<MySongs />
<Search />
</div>
</div>
);
}
export default App;
.App {
background-color: #303030;
width: 100%;
height: 100vh;
}
.body {
display: flex;
}
I have Search.jsx
import React, { useState, useEffect, useRef } from "react";
import "./Search.css";
import styled from 'styled-components';
import { IoSearch,IoClose } from "react-icons/io5";
import {motion, AnimatePresence} from "framer-motion";
import {useClickOutside} from "react-click-outside-hook";
import MoonLoader from 'react-spinners/MoonLoader';
import { useDebounce } from "./hooks/debounceHook";
import axios from "axios";
import { TvShow } from "./tvShow";
const SearchBarContainer = styled(motion.div)`
margin-left: 10px;
margin-top: 20px;
display: flex;
flex-direction: column;
width: 96%;
height: 2.5em;
background-color: #424242;
border-radius: 3px;
`;
const SearchInputContainer = styled.div`
width: 98%;
min-height: 2.5em;
display: flex;
align-items: center;
position: relative;
padding: 2px 15px;
`;
const SearchInput = styled.input`
width: 100%;
height: 100%;
outline: none;
border: none;
font-size: 15px;
color: white;
font-weight: 300;
border-radius: 6px;
background-color: transparent;
&:focus {
outline: none;
&::placeholder {
opacity: 0;
}
}
&::placeholder {
color: #white;
transition: all 250ms ease-in-out;
}
`;
const SearchIcon = styled.span`
color: #bebebe;
font-size: 14px;
margin-right: 10px;
margin-top: 6px;
vertical-align: middle;
`;
const CloseIcon = styled(motion.span)`
color: #bebebe;
font-size: 15px;
vertical-align: middle;
transition: all 200ms ease-in-out;
cursor: pointer;
&:hover {
color: #dfdfdf;
}
`;
const LineSeperator = styled.span`
display: flex;
min-width: 100%;
min-height: 2px;
background-color: #d8d8d878;
`;
const SearchContent = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 1em;
overflow-y: auto;
`;
const LoadingWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
const WarningMessage = styled.span`
color: #a1a1a1;
font-size: 14px;
display: flex;
align-self: center;
justify-self: center;
`;
const containerVariants = {
expanded: {
height: "26em",
},
collapsed: {
height: "2.5em",
},
};
const containerTransition = { type: "spring", damping: 22, stiffness: 150 };
export function SearchBar(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [tvShows, setTvShows] = useState([]);
const [noTvShows, setNoTvShows] = useState(false);
const isEmpty = !tvShows || tvShows.length === 0;
const changeHandler = (e) => {
e.preventDefault();
if (e.target.value.trim() === "") setNoTvShows(false);
setSearchQuery(e.target.value);
};
const expandContainer = () => {
setExpanded(true);
};
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoTvShows(false);
setTvShows([]);
if (inputRef.current) inputRef.current.value = "";
};
useEffect(() => {
if (isClickedOutside) collapseContainer();
}, [isClickedOutside]);
const searchTvShow = async () => {
if (!searchQuery || searchQuery.trim() === "") return;
setLoading(true);
setNoTvShows(false);
const options = {
method: 'GET',
url: 'https://deezerdevs-deezer.p.rapidapi.com/search',
params: {q: searchQuery},
headers: {
'x-rapidapi-host': 'deezerdevs-deezer.p.rapidapi.com',
'x-rapidapi-key': '6a99d5e101msh1e9f2b2f948746fp1ae1f3jsn6b458fe8b4e4'
}
};
axios.request(options).then(function (response) {
if (response) {
if (response.data && response.data.length === 0) setNoTvShows(true);
setTvShows(response.data.data);
}
}).catch(function (error) {
console.error(error);
});
setLoading(false);
};
useDebounce(searchQuery, 500, searchTvShow);
// console.log(tvShows);
return (
<div className="my__search">
<SearchBarContainer
animate={isExpanded ? "expanded" : "collapsed"}
variants={containerVariants}
transition={containerTransition}
ref={parentRef}
>
<SearchInputContainer>
<SearchIcon>
<IoSearch />
</SearchIcon>
<SearchInput
placeholder="Search for Series/Shows"
onFocus={expandContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (
<CloseIcon
key="close-icon"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={collapseContainer}
transition={{ duration: 0.2 }}
>
<IoClose />
</CloseIcon>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator />}
{isExpanded && (
<SearchContent>
{isLoading && (
<LoadingWrapper>
<MoonLoader loading color="#000" size={20} />
</LoadingWrapper>
)}
{!isLoading && isEmpty && !noTvShows && (
<LoadingWrapper>
<WarningMessage>Start typing to Search</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && noTvShows && (
<LoadingWrapper>
<WarningMessage>No Tv Shows or Series found!</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && !isEmpty && (
<>
{tvShows.map((show) => (
<TvShow
key={show.id}
thumbnailSrc={show.album.cover_medium}
name={show.title_short}
artist={show.artist.name}
/>
))}
</>
)}
</SearchContent>
)}
</SearchBarContainer>
</div>
);
}
export default SearchBar;
.my__search {
margin-top: 20px;
flex: 0.6;
height: 450px;
border-radius: 5px;
border: 1px solid black;
margin-left: 80px;
background-color: #424242;
}
I have tvShow.jsx
import React, { useState } from "react";
import styled from "styled-components";
import {ImDownload} from "react-icons/im";
const TvShowContainer = styled.div`
width: 96%%;
min-height: 3em;
display: flex;
border-bottom: 2px solid #555555;
align-items: center;
`;
const Thumbnail = styled.div`
width: auto;
height: 80%;
display: flex;
flex: 0.4;
img {
border-radius: 20px;
width: auto;
height: 100%;
}
`;
const Name = styled.h3`
font-size: 12px;
color: white;
flex: 2;
display: flex;
flex-direction: column;
`;
const Artist = styled.span`
margin-top: 10px;
font-size: 8px;
color: white;
display: flex;
align-items: center;
`;
const Rating = styled.span`
color: #a1a1a1;
font-size: 16px;
display: flex;
flex: 0.2;
`;
export function TvShow(props) {
const { thumbnailSrc, name, artist,clickedMusic } = props;
const [wantedMusic, setWantedMusic] = useState("");
// const [clickedShow, setClickedShow] = useState("");
// function clickedContainer(e){
// const element = e.currentTarget();
// setClickedShow(element);
// console.log("I am clickedShow " +clickedShow);
// }
return (
<TvShowContainer onclick="location.href='#';" >
<Thumbnail>
<img src={thumbnailSrc} />
</Thumbnail>
<Name>{name}
<Artist>
{artist}
</Artist>
</Name>
</TvShowContainer>
);
}
I have mySongs.js
import React from "react";
import "./MySongs.css";
function MySongs() {
return (
<div className="my__songs">
<p>Your Playlist</p>
</div>
);
}
export default MySongs;
.my__songs {
margin-left: 10px;
margin-top: 20px;
flex: 0.3;
height: 300px;
height: 450px;
border: 1px solid black;
border-radius: 5px;
background-color: #424242;
}
.my__songs > p {
color: white;
opacity: 90%;
margin-left: 10px;
font-size: 13px;
}
Only partly answering the question: move an item from one list to another (and back) on mouse click.
The basic situation can be solved if you use the parent component to hold the state that the children components display. Then you only need to implement a function that toggles a "flag" (like selected), and the components can be rendered based on that flag.
const {useState} = React
const tracklist = [
{
id: 1,
title: 'Track 1',
selected: false,
},
{
id: 2,
title: 'Track 2',
selected: false,
},
{
id: 3,
title: 'Track 3',
selected: false,
},
{
id: 4,
title: 'Track 4',
selected: false,
},
{
id: 5,
title: 'Track 5',
selected: false,
},
]
const ListItem = ({ title, onToggleSelect }) => <div className="list-item" onClick={onToggleSelect}>{title}</div>
const App = ({ tracklist }) => {
const [tracks, setTracks] = useState(tracklist)
const toggleSelect = (id) => {
setTracks((prevState) => prevState.map(item => item.id === id ? {...item, selected: !item.selected} : item))
}
const listItem = (track) => <ListItem key={track.id} {...track} onToggleSelect={() => toggleSelect(track.id)}/>
return (
<div className="container" >
<div className="tracklist">
{
tracks
.filter(({ selected }) => selected)
.map(listItem)
}
</div>
<div className="tracklist">
{
tracks
.filter(({ selected }) => !selected)
.map(listItem)
}
</div>
</div>
)
}
ReactDOM.render(
<App tracklist={tracklist} />,
document.getElementById('root')
);
html, body {
margin: 0;
padding: 5px 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
}
.list-item {
cursor: pointer;
}
.list-item:hover {
background: lightgray;
}
.tracklist {
border: 1px solid gray;
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>

React mobile nav won't close automatically when navigating to another route

I have built a mobile nav following this tutorial which is a fantastic tutorial, however I am stuck.
The mobile nav dropdown opens and closes no problem when clicking on the nav icon. However, when I click on a dropdown menu link and it navigates to another page, the mobile nav will stay open unless I manually close it myself.
How can I get the mobile nav to close when the route has changed?
I have a feeling its the Navbar file that isn't quite right! But I can't seem to get my head around it. Any suggestions would be great.
Thank you.
/// Navbar.jsx
import { Alert, Stack } from '#mui/material'
import React, { useContext, useState } from 'react'
import '../../styles/nav.css'
import { UserContext } from '../Contexts/User-Context'
const Navbar = (props) => {
const { loggedInUser } = useContext(UserContext)
const [open, setOpen] = useState(false)
const showSidebar = () => {
setOpen(!open)
}
const DropdownItem = (props) => {
return (
<div>
{ props.children }
</div>
)
}
return (
<div className='navbar'>
<h2 className='logo'>NC Games</h2>
<ul className='navbar-list'>
{ props.children }
</ul>
</div>
)
}
export default Navbar
/// DropdownMenu.jsx
import React, { useContext, useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { UserContext } from '../Contexts/User-Context'
const DropdownMenu = () => {
const { loggedInUser, setLoggedInUser } = useContext(UserContext)
const [open, setOpen] = useState(false)
const logOut = () => {
setLoggedInUser({ user: undefined })
}
const DropdownItem = (props) => {
return (
<div className='menu-item'>
{ props.children }
</div>
)
}
return loggedInUser ? (
<><div className='dropdown'>
<Link to={`/users/${loggedInUser.username}`}>
<DropdownItem className='nav-profile'>
<img className='nav-profile-pic' alt={loggedInUser.username} src={loggedInUser.avatar_url} />
</DropdownItem>
</Link>
<Link to={`/categories`}>
<DropdownItem>Categories</DropdownItem>
</Link>
<Link to={`/reviews`}>
<DropdownItem>Reviews</DropdownItem>
</Link>
<Link to={`/users`}>
<DropdownItem>Users</DropdownItem>
</Link>
<Link onClick={() => logOut()} to={`/`}>
<DropdownItem>Log out</DropdownItem>
</Link>
</div>
</>
) : (
null
)
}
export default DropdownMenu
/// NavItem.jsx
import React, { useState } from 'react'
import '../../styles/nav.css'
const NavItem = (props) => {
const [open, setOpen] = useState(false)
console.log(open, "<< open");
return (
<li className='nav-item'>
<div onClick={() => setOpen(!open)}>
{ props.icon }
</div>
{ open && props.children }
</li>
)
}
export default NavItem
// App.jsx
function App() {
// const [user, setUser] = useState(null);
const [loggedInUser, setLoggedInUser] = useState(null);
const [loggedIn, setLoggedIn] = useState(false);
// const response = await axios.post()
const saveLoggedInUser = () => {
localStorage.setItem("user", JSON.stringify(loggedInUser));
};
const getLoggedInUser = () => {
if (localStorage.getItem("user") === null) {
localStorage.setItem("user", JSON.stringify(null));
} else {
let userLocal = JSON.parse(localStorage.getItem("user"));
setLoggedInUser(userLocal);
setLoggedIn(true);
}
};
useEffect(() => {
getLoggedInUser();
}, []);
useEffect(() => {
saveLoggedInUser();
}, [loggedInUser, loggedIn]);
useEffect(() => {
window.scrollTo(50, 50)
}, [])
const isLoggedIn = loggedInUser !== null;
return (
<BrowserRouter>
<UserContext.Provider
value={{ loggedInUser, setLoggedInUser, isLoggedIn }}
>
<div className="App">
<Navbar>
<NavItem icon={<MenuRoundedIcon />}>
<DropdownMenu />
</NavItem>
</Navbar>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/categories" element={<Categories />} />
<Route path="/reviews/:category" element={<ReviewList />} />
<Route path="/reviews" element={<ReviewList />} />
<Route path="/review/:review_id" element={<ReviewPage />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:username" element={<User />} />
</Routes>
</div>
</UserContext.Provider>
</BrowserRouter>
);
}
/// Nav.css
* {
margin: 0px;
}
:root {
--bg: #242526;
--bg-accent: #484a4d;
--nav-size: 60px;
--border: 1px solid #474a4d;
--border-radius: 8px;
--speed: 500ms;
/* margin-top: 75px; */
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
a {
/* color: #dadce1; */
text-decoration: none;
}
.logo {
color: #dadce1;
margin: 0px;
display: flex;
align-items: center;
}
.navbar {
height: var(--nav-size);
background-color: var(--bg);
padding: 0 1rem;
border-bottom: var(--border);
display: flex;
justify-content: space-between;
position: fixed;
z-index: 1000;
top: 0;
width: 100%;
}
.navbar-list {
max-width: 100%;
height: 100%;
display: flex;
justify-content: flex-end;
}
.nav-item {
width: calc(var(--nav-size) * 0.8);
display: flex;
align-items: center;
justify-content: center;
margin-right: 25px;
color: #dadce1;
}
.icon-button {
--button-size: calc(var(--nav-size) * 0.5);
width: var(--button-size);
height: var(--button-size);
/* background-color: #484a4d; */
border-radius: 50%;
padding: 5px;
margin: 2px;
display: flex;
justify-content: center;
align-items: center;
}
.dropdown {
position: absolute;
z-index: 1;
top: 56px;
width: 300px;
transform: translateX(-35%);
background-color: var(--bg);
border: var(--border);
border-radius: var(--border-radius);
padding: 1rem;
overflow: hidden;
display: flex;
flex-direction: column;
opacity: 1;
}
.menu-item {
height: 50px;
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
border-radius: var(--border-radius);
/* transition: background var(--speed); */
padding: 0.5rem;
}
.menu-item:hover {
background-color: #525357;
}
.nav-profile-pic {
max-width: 75px;
padding-bottom: 40px 0px 40px 0px;
object-fit: contain;
overflow: hidden;
border-radius: 50%;
}
Do you have your NavBar component defined inside each route?
I think if you move your NavBar into each page, then by default the header / navbar should be reinitialized each time the route changes.
You can also use useLocation() and destructure pathname from that. Then use a useEffect to monitor that for changes and close the dropdown on change:
const { path } = useLocation();
useEffect(() =>{
setDropDownOpen(false);
}, [path])

sticky navbar in React

I need to bring the sticky header. When the user scrolls I need to add this sticky header. I've kept offset greater than 200 and added the respective css code. While I am debugging the code offset is printing correctly but by scrolled site is not getting appended. Any one can guide me what I am doing wrong. Below I have added both style and logic. Thanks in advance!
JSX:
import React, { useState, useEffect } from "react"
import "../styles/global.css"
export default function Navbar() {
const [scrolled, setScrolled] = useState(false)
const handleScroll = () => {
const offset = window.scrollY
console.log("OFFSET VALUE", offset)
if (offset > 200) {
setScrolled(true)
} else {
setScrolled(false)
}
}
useEffect(() => {
window.addEventListener("scroll", handleScroll)
})
let x = ["site-header"]
if (scrolled) {
x.push("scrolled")
}
return (
<>
<header className={x.join("")}>
<div className="wrapper site-header__wrapper">
<h2 className="brand">Community Site</h2>
<nav className="nav">
<div className="nav__wrapper">
<Link className="nav__item" to="/Home">
<h3> Home</h3>
</Link>
<Link className="nav__item" to="/Aboutus">
<h3>What are we</h3>
</Link>
<Link className="nav__item " to="/Contactus">
<h3>Contact Us</h3>
</Link>
</div>
</nav>
</div>
</header>
</>
)
}
CSS:
.site-header {
width: 100%;
max-width: 100%;
box-sizing: border-box;
/* position: relative;
overflow: hidden; */
padding: 15px 10px;
background-color: white;
border: 1px white;
transition: all 0.12s ease;
}
.scrolled {
position: sticky;
top: 0;
left: 0;
background-color: red;
}
.site-header__wrapper {
padding-top: 4rem;
padding-bottom: 6rem;
}
#media (min-width: 600px) {
.site-header__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 0;
padding-bottom: 15px;
}
}
#media (min-width: 600px) {
.nav__wrapper {
display: flex;
gap: 12px;
align-items: center;
padding-top: 10px;
padding-bottom: 0;
}
}
I ran some tests and I think I know what happen, you have the class x.join("") however I manually added "site-header" and it worked by also adding an inline style
<header
className={site-header'}
style={scrolled ? { opacity: "1" } : { opacity: "0" }}
>
I also modified the site-header by adding position: fixed; top: 0
If you want to achieve the same result by just adding the class, I recommend you to use properties like opacity or transform: translateY() and add some transition to it
You can try this code.
const [scrolling, setScrolling] = useState(false);
const [scrollTop, setScrollTop] = useState(0);
useEffect(() => {
const onScroll = (e) => {
setScrollTop(e.target.documentElement.scrollTop);
if(scrollTop > 200) {
setScrolling(true)
};
};
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, [scrollTop, scrolling]);

How to position a div relatively to an image?

I have a page for a portfolio that does contain a grid that contain images with an info overlay.
Here's the link: cyrilmoisson-dev.netlify.app
Is there a solution to make the overlay div exactly the same size (height and width) as the image without using something like background: url(...);
The problem is that images are random sized...
This question is not a duplicate of this one because it hasn't been resolved for me.
Here is the component code for every image:
src/component/ImageWithInfos/ImageWithInfos.jsx:
// Lazyload
import LazyLoad from 'react-lazyload';
// Style
import { ImageContainer, ImageSrc, ImageInfoContainer, ImageInfo } from './styles';
// Utils
import PropTypes from 'prop-types';
import { v4 as uuid } from 'uuid';
const ImageWithInfos = ({ height, width, src, title, infos }) => (
<LazyLoad height={height} offset={height + 100}>
<ImageContainer height={height} width={width}>
<ImageSrc src={src} alt={title} />
<ImageInfoContainer>
<ImageInfo main>{title}</ImageInfo>
{infos.map((info) => <ImageInfo key={uuid()}>{info}</ImageInfo>)}
</ImageInfoContainer>
</ImageContainer>
</LazyLoad>
);
ImageWithInfos.propTypes = {
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
src: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
infos: PropTypes.array,
};
export default ImageWithInfos;
src/component/ImageWithInfos/styles.js
// Style
import styled, { css } from 'styled-components';
export const ImageContainer = styled.div`
height: ${({ height }) => `${height}px`};
width: ${({ width }) => `${width}px`};
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
`;
export const ImageSrc = styled.img`
display: block;
object-fit: contain;
width: 100%;
height: 100%;
`;
export const ImageInfoContainer = styled.div`
z-index: 5;
position: absolute;
bottom: 0;
height: 100%;
width: 100%;
opacity: 0;
transition: 1s ease;
background-color: black;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&:hover {
opacity: 1;
background-color: rgba(0, 0, 0, .7);
scale: 1.1;
}
`;
export const ImageInfo = styled.span`
padding: .2rem 1rem;
color: whitesmoke;
text-align: center;
text-transform: capitalize;
${({ main }) => main && css`
font-weight: 800;
`}
`;
src/component/ImageWithInfos/index.js
export { default } from './ImageWithInfos';
Thanks for your help.
BTW: I'm using react and styled-components, if it changes anything...
I believe you could place both the image and the overlay in the same div and have the overlay element cover the whole parent div:
<div className="parent">
<img />
<div className="overlay"></div>
</div>
.parent {
position: relative;
}
.overlay {
position: absolute;
width: 100%;
height: 100%;
}

Categories

Resources