I have a function that triggers onClick which adds borders to the clicked element.
Here is the code in the component:
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Logo from '../../components/logo.svg';
import React from 'react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
const {
publicRuntimeConfig: { domain },
} = getConfig();
export default function MediaNav() {
// If router equals link in list, show blue border
const router = useRouter();
const menu = ['Home', 'FAQs', 'Contact Us', 'Social Media'];
const [selectedPage, setPage] = useState({
borderColor: '',
});
const handleLinkClick = e => {
setPage({
borderStyle: '3px solid #005ba9',
});
e.target.style.borderLeft = selectedPage.borderStyle;
e.target.style.borderRight = selectedPage.borderStyle;
};
return (
<nav>
<div>
<Link href={`${domain}`}>
<a>
<Logo />
</a>
</Link>
</div>
<ul
>
<div>
<ul>
<li
>
All Sites
</li>
</ul>
<ul
>
{menu.map((item, i) => {
return (
<li key={i}>
<Link href={item === 'Home' ? '/subdomain/link' : `/subdomain/${item}`.replace(/\s+/g, '').toLowerCase()}>
<a onClick={handleLinkClick}>
{item}
</a>
</Link>
</li>
);
})}
</ul>
) : null}
</div>
</ul>
</nav>
);
}
At the moment, when the element in the link list is clicked, the blue border is being applied, however, if I click another link, I wanted the previously clicked element link to be removed its borders.
As this is NextJs and I have a Link tag wrapping up the link element, loading is not occurring, therefore I don't know how to make a difference between previously clicked element and currently clicked element.
Any idea how to remove the borders to already clicked link when next link is clicked?
I think a better approach to this problem would be having a state to save currentPage user visiting and depending on currentPage state giving a style to a element.
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Logo from '../../components/logo.svg';
import React from 'react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
const {
publicRuntimeConfig: { domain },
} = getConfig();
export default function MediaNav() {
// If router equals link in list, show blue border
const router = useRouter();
const menu = ['Home', 'FAQs', 'Contact Us', 'Social Media'];
const [currentPage, setCurrentPage] = useState('')
const handleLinkClick = pageName => {
setCurrentPage(pageName);
};
return (
<nav>
<div>
<Link href={`${domain}`}>
<a>
<Logo />
</a>
</Link>
</div>
<ul
>
<div>
<ul>
<li
>
All Sites
</li>
</ul>
<ul>
{menu.map((item, i) => {
return (
<li key={i}>
<Link href={item === 'Home' ? '/subdomain/link' : `/subdomain/${item}`.replace(/\s+/g, '').toLowerCase()}>
<a onClick={() => handleLinkClick(item)} style={{ border: currentPage === item ? '3px solid #005ba9': 'initial' }}>
{item}
</a>
</Link>
</li>
);
})}
</ul>
</div>
</ul>
</nav>
);
}
I think instead of adding a style on a target element, you can try adding a class, let's say highlight on the element.
highlight class would look like -
.highlight {
border-left: '3px solid #005ba9',
border-right: '3px solid #005ba9'
}
So only the elements which have this class would show the border styles applied. Since the components would re-render on state changes, make sure you are applying the highlight class on the link in map function logic. This would ensure other elements won't have this class and this would solve the problem.
PS: Removing of the previously applied styles would be tedious. I think the above solution would work without much hassle and it would work great.
Related
I am making my website portfolio and I have created a Navbar component with links to home page, project page and blog page. The Home page and blog page are internal links while the projects link is an external link.
Now the way I have this setup is like this:
import * as React from 'react';
import { Link, useStaticQuery, graphql } from 'gatsby';
import Icon from '../images/icon.svg';
const Navbar = ({pageTitle}) => {
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`);
const menu = [
{ title: 'HOME', path:'/' },
{ title: 'PRØJECTS', path: 'https://github.com/chukkyiii?tab=repositories' },
{ title: 'BLØG', path: '/blog' }
]
return (
<main>
<title>
{pageTitle} | {data.site.siteMetadata.title}
</title>
<div>
<div className="mx-auto mb-9 flex items-center justify-between max-w-2xl ">
<Icon className="w-12 h-12 p-1 rounded-full ring-2 ring-gray-300 dark:ring-gray-500" />
<nav>
<ul className="flex">
{menu.map((item) => (
<li key={item.title}>
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
</li>
))}
</ul>
</nav>
</div>
</div>
</main>
);
}
export default Navbar;
So I don't have to repeat the same line of code again, I just used a Link which works but it gives me a warning, is there a different way I can do this without the warning?
the warning it gives:
react_devtools_backend.js:4026 External link https://github.com/chukkyiii?tab=repositories was detected in a Link component. Use the Link component only for internal links. See: https://gatsby.dev/internal-links
...
You can easily customize your menu array to display a new property to base your loop on:
const menu = [
{ title: 'HOME', path:'/', isInternal: true },
{ title: 'PRØJECTS', path: 'https://github.com/chukkyiii?tab=repositories', isInternal: false },
{ title: 'BLØG', path: '/blog', true }
]
Then:
{
menu.map((item) => {
if (item.isInternal) {
return (
<li key={item.title}>
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
</li>
);
}
return (
<li key={item.title}>
<a href={item.path} target="_blank">
{item.title}
</a>
<li key={item.title}>
);
});
}
You can even do a ternary condition inside the return to take advantage of the <li> wrapper:
{
menu.map((item) => (
<li key={item.title}>
{item.isInternal ? (
<Link to={item.path} className="pl-8 hover:text-amber-500">
{item.title}
</Link>
) : (
<a href={item.path} target="_blank">
{item.title}
</a>
)}
</li>
));
}
Link component is intended to use only for internal navigation, somethign what happens in the scope of your application and React/Gatsby can be aware of. If the link points to any external source (like GitHub in this case), you need to use the standard anchor (which in fact is what it renders the Link component).
The same approach of adding the isInternal attribute (which personally I find it more succinct) can be done in the same way using a regular expression or similar, just checking how the path is.
I started to learn to code recently and to create my portfolio. I am working on the header and I would like to change the text color header link tag when it is only clicked once.
When I clicked the link, the text color changed to red and when I clicked the link now but when I click another link, the previous link is still red.
I would like to change the text color back from red to gray when I click another.
Example:- OK - home about work skill contact NO - home about work skill contact
Header.jsx
import React, { useState } from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage } = props;
const [onClickChangeColor, setOnClickChangeColor] = useState(false);
const redText = () => {
setOnClickChangeColor(!onClickChangeColor);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={onClickChangeColor ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
HeaderLayout.jsx
import React from "react";
import Header from "./Header";
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header headerPage={headerPage}>{headerPage}</Header>
))}
<div
className="resume"
class="w-24 h-8 text-[#FF5757] border border-[#FF5757] rounded-md"
>
<a
href="https://drive.google.com/file/d/usp=sharing"
class="block"
>
resume
</a>
</div>
</div>
);
};
export default HeaderLayout;
I am using React and Tailwind. English is my second language and I hope everyone understands what I want to do. Thank you in advance.
change the color handling state to parent (Header Layout)
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
const [selectedHeader, changeSelectedHeader] = useState("");
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header
headerPage={headerPage}
changeSelectedHeader={changeSelectedHeader}
selectedHeader={selectedHeader}
>
{headerPage}
</Header>
))}
then, make the child like this
import React from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage, changeSelectedHeader, selectedHeader } = props;
const redText = () => {
changeSelectedHeader(headerPage);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={headerPage === selectedHeader ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
You can track the selected item index in the HeaderLayout component and then use it to decide the header item color depending on that.
Add another state to maintain the selected header item state in HeaderLayout.
const [selectedIndex, setSelectedIndex] = useState(-1);
Provide the headerIndex, selectedIndex, and setSelectedIndex as props to the Header compoenent.
{headerPages.map((headerPage, headerIndex) => (
<Header
headerPage={headerPage}
setSelectedIndex={setSelectedIndex}
selectedIndex={selectedIndex}
headerIndex={headerIndex}
>
{headerPage}
</Header>
))}
In the Header component get the props and set the header item CSS as below.
const { headerPage, selectedIndex, setSelectedIndex, headerIndex } = props;
...
...
<ScrollLink
className={headerIndex === selectedIndex ? "text-[#FF5757]" : ""}
onClick={() => setSelectedIndex(headerIndex)}
to={headerPage}
smooth={true}
duration={500}
>
Working Demo
So I have a sidebar, and when I click on one of the items, it should scroll to div with a certain name. It worked before, but I decided to add 'active' class to the item clicked to change its color, and it stopped scrolling to divs.
Sidebar.jsx file:
import React, { useState} from 'react';
import './Sidebar.scss';
import { SlidebarData } from "./SlidebarData";
import spike from '../../assets/spike_pic.jpg';
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [selectedLink, setSelectedLink] = useState("");
return (
<div className="sidebar">
<span className="btn" onClick={() => setSidebar(!sideBar)}>Menu</span>
<div className="profile">
<img src={spike}/>
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val,key) =>{
return (
<li
className={`row ${selectedLink === val.link ? "active" : ""}`}
id={val.link}
key={key}
onClick={()=> {
setSelectedLink(val.link);
document.getElementById(val.link).scrollIntoView();
}}>
{""}
<div>
{val.title}
</div>
</li>
);
})}
</ul>
</div>
);
}
export default Sidebar;
As you see, I have a setState hook for selectedlink, which adds active class, but scrollIntoView doesn't work because of it, and I can't figure out why.
SidebarData.jsx, where I get links from:
export const SlidebarData = [
{
title: "Home",
link: "home"
},
{
title: "About",
link: "about"
},
{
title: "Services",
link: "services"
},
{
title: "Contact",
link: "contact"
}
]
I'm trying to make a SPA with react and using styled-component for styling and theme changing. The problem i have when i add classes by Javascript and then try to change theme all the classes added by JavaScript are removed.
Can someone tell me what is the reason for this and how can i solve this problem?
Here is the example, where you'll see that when you scroll down on i add class to header with help of JavaScript to give bottom border but when i toggle theme with button then added class is removed.
const themeToggler = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
const StyleHeader = styled.header `
background: ${(props) => props.theme.body};
`;
function scrollPos(maxScrollPos) {
let scrollPos = document.documentElement.scrollTop;
if (scrollPos >= maxScrollPos) {
addClassToElement("glow-shadow", ".header");
} else {
removeClassFromElement("glow-shadow", ".header");
}
}
<ThemeProvider theme={theme==="light" ? light : dark}>
<GlobalStyles />
<StyleHeader className={`header pt-3 pb-3 fixed-top`}>
<button onClick={themeToggler}>Toggle</button>
</StyleHeader>
<div className="content">
scrool down to see border on header then click the toggle button to change the theme and border class will be removed
<h1>h1</h1>
</div>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
</ThemeProvider>
You need to run your scrollPos function after theme change. The theme switcher apparently removes all but the original classes on the element, so you need to replace them. Something like this, where we watch the theme property for changes:
import React, { useEffect } from "react";
export default function App() {
...
useEffect(() => {
scrollPos(100);
}, [theme, scrollPos]);
...
};
Demo
I accepted the answer from #isherwood but i found another way so i would like to share it.
import React, { useState} from "react";
export default function App() {
...
const [isScrolled, setIsScrolled] = useState(false);
function scrollPos(maxScrollPos) {
let scrollPos = document.documentElement.scrollTop;
if (scrollPos >= maxScrollPos) {
setIsScrolled(true);
} else {
setIsScrolled(false);
}
}
window.addEventListener("scroll", (event) => {
scrollPos(100);
});
return (
<ThemeProvider theme={theme==="light" ? light : dark}>
<GlobalStyles />
<StyleHeader className={`nq-header pt-3 pb-3 fixed-top ${
isScrolled ? "glow-shadow" : ""
}`}>
<button onClick={themeToggler}>Toggle</button>
</StyleHeader>
<div className="content">
scrool down to see border on header then click the toggle button to change the theme and border class will be removed
<h1>h1</h1>
</div>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
</ThemeProvider>
)};
I am building a gallery where you click on the image and it will load in a separate component using props, this image is a URL, taken from a hard-coded array, where the src is loaded as a background image via CSS. My challenge is connecting the data to that component. I have tried connecting the data from parent to child with callbacks, but no luck. I think what I am trying to do is connect components sideways, and I don't want to use redux, as I am not familiar.
Note: I am aware you can just load the image in GalleryContainer.js using window.location.href = "props.src/", however, I want the image to load in the Image component that will act as a container to hold the image giving the user other options such as downloading the image, etc...
Note: I have tried importing the Image component in Gallery.js and rendering it like so: <Image src={props.src} id={props.id}/>, and I find the data connects just fine, but this does not help keep the component separate.
What I have already :
I have a route in app.js that allows me to go to the image route path just fine it’s loading in the url from props.src in the image component that is my challenge
UPDATE: SOLVED Click here to see the solution!
Here is the code:
GalleryList.js
import Gallery from "./Gallery";
import Header from "./UI/Header";
import Footer from "./UI/Footer";
import styles from "./Gallery.module.css";
const DUMMY_IMAGES = [
{
id: "img1",
src: "https://photos.smugmug.com/photos/i-vbN8fNz/1/X3/i-vbN8fNz-X3.jpg",
},
{
id: "img2",
src: "https://photos.smugmug.com/photos/i-fSkvQJS/1/X3/i-fSkvQJS-X3.jpg",
},
{
id: "img3",
src: "https://photos.smugmug.com/photos/i-pS99jb4/0/X3/i-pS99jb4-X3.jpg",
},
];
const GalleryList = () => {
const imagesList = DUMMY_IMAGES.map((image) => (
<Gallery id={image.id} key={image.id} src={image.src} />
));
return (
<>
<Header />
<ul className={styles.wrapper}>
<li className={styles.list}>{imagesList}</li>
</ul>
Home
<Footer />
</>
);
};
export default GalleryList;
Gallery.js
import GalleryConatiner from "./UI/GalleryContainer";
import styles from "./Gallery.module.css";
const Gallery = (props) => {
return (
<>
<div className={styles["gal-warp"]}>
<GalleryConatiner id={props.id} key={props.id} src={props.src} />
</div>
</>
);
};
export default Gallery;
GalleryContainer.js
import styles from "../Gallery.module.css";
const GalleryConatiner = (props) => {
const selectedImg = () => {
if (props.id) {
// window.location.href = `image/${props.src}`;
window.location.href = "image/"
}
};
return (
<ul>
<li className={styles["gallery-list"]}>
<div
onClick={selectedImg}
className={styles["div-gallery"]}
style={{
backgroundImage: `url(${props.src}`,
height: 250,
backgroundSize: "cover",
}}
></div>
</li>
</ul>
);
};
export default GalleryConatiner;
Image.js
import styles from "./Image.module.css";
const Image = (props) => {
return (
<section>
<h1 className={styles["h1-wrapper"]}>Image:{props.id}</h1>
<div className={styles.wrapper}>
<div
className={styles["image-container"]}
style={{
backgroundImage: `url(${props.src}`,
}}
></div>
</div>
</section>
);
};
export default Image;
You should be able to use the router Link to pass data via "state" on the to property.
From React Router's documentation:
<Link
to={{
pathname: "/images",
state: { imgUrl: props.src }
}}
/>