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.
Related
I want to add a hyperlink by put that url info to data array.
But the anchor tag with href does not work as I thought.
Should I use react-router-dom's Link tag? Or would there be any clue to add hyperlink when I add {link} on Goods.js?
#1 Shop.js
export const Shop = () => {
return (
<div className="shop">
<div className="shop-title">
<h1>Anime Biased Shop</h1>
</div>
<div className="items">
{ItemsList.map((item) => (
<Goods shopItemProps={item} />
))}
</div>
</div>
);
};
#2 Goods.js
import React, { useState } from "react";
export const Goods = (props) => {
const { id, name, price, image, link } = props.shopItemProps;
const [count, setCount] = useState(0);
return (
<>
<div className="goods">
<div>{id}</div>
<img src={image} alt="thumbnail_image" />
<div className="goods-name">{name}</div>
<div className="goods-price">${price}</div>
<a href={link} />
<div className="cart-button">
<button onClick={() => setCount(count - 1)}>-</button>
{count}
<button onClick={() => setCount(count + 1)}>+</button>
</div>
</div>
</>
);
};
#3 ItemsList.js (the data array file I mentioned)
export const ItemsList = [
{
id: 1,
name: "VA-11 Hall-A: Cyberpunk Bartender Action",
price: 110000,
image: cover1,
link: "https://store.steampowered.com/app/447530/VA11_HallA_Cyberpunk_Bartender_Action/?l=koreana",
},
There are two options.
1. External links or redirect with page refresh
If it is external link, to other website you should use a. This looks nice
<a href={link} />
but can be not visible, because you do not have any content in this link. You can change it to:
<a href={link}><button>Click me</button></a>
2. Internal links without page reload
If it is internal link, you should remove domain, eg by replace function and use Link if you using next
<Link href="/about">About Us</Link>
https://nextjs.org/docs/api-reference/next/link
or Redirect if you using react-router-dom
<Redirect to='/profile' />
https://www.telerik.com/blogs/programmatically-navigate-with-react-router
In both cases you should add something inside of this element or add classes that make id visible.
You Can Test click me
or React Router Dom like this :
const data = [
{
name: "john",
profile: "/profile",
linkTo: "/user/john",
},
];
{
data.map((item, index) => {
return (
<Link to={item.linkTo}>
<h1>name:{item.name}</h1>
<Link to={item.profile}>
<h5>editProfile</h5>
</Link>
</Link>
);
});
}
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.
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 }
}}
/>
element.find('span.active-nav-option') returns nothing whilst element.find('.active-nav-option') returns the span. The point of the test is to find out if a span was rendered instead of a Link.
Component is as follows:
const PageNav = ({
router,
slides,
}) => (
<nav className="PageNav">
<span className="chevron">
<MoreVerticalIcon
strokeWidth="0.5px"
size="3em"
/>
</span>
<ul className="nav-links">
{mapSlides(slides, router)}
</ul>
<style jsx>{styles}</style>
</nav>
)
function mapSlides(slides, router) {
return Object.entries(slides)
.sort((
[, { order: a }],
[, { order: b }],
) => a - b)
.map(([slidename, { altText, order }]) => {
const isActiveLink = router.query.slidename === slidename
const navItemClassnames = [
'nav-item',
isActiveLink && 'active',
]
.filter(Boolean)
.join(' ')
const Element = isActiveLink
? props => <span {...props} />
: Link
const liInternalElementProps = {
...(isActiveLink && { className: 'active-nav-option' }),
...(!isActiveLink && {
href: `/CVSlide?slidename=${slidename}`,
as: `/cv/${slidename}`,
}),
}
return (
<li
className={navItemClassnames}
key={order}
>
<Element {...liInternalElementProps}>
<a title={altText}>
<img
src={`/static/img/nav-icons/${slidename}.svg`}
alt={`An icon for the ${slidename} page, ${altText}`}
/>
</a>
</Element>
<style jsx>{styles}</style>
</li>
)
})
}
To Reproduce
run this line as a test:
const wrapperOne = shallow(
<PageNav
slides={mockSlides}
router={{
query: {
slidename: 'hmmm',
},
}}
/>
)
const spanExists = wrapperOne
.find('.active-nav-option')
.html() // outputs <span class="active-nav-option">...</span>
// so one would expect span.active-nav-option to work?
const spanDoesNotExist = wrapperOne
.find('span.active-nav-option')
.html() // throws an error `Method “html” is only meant to be run on a single node. 0 found instead.`
// subsequently if I use `.exists()` to test if the element exists, it returns nothing.
Expected behavior
element.find('span.active-nav-option') should return the span. I think? I initially thought this was to do with shallow vs mount but the same happens with mount. Am I being an idiot here? Is this something to do with the map function in the component?
OS: OSX
Jest 23.5
enzyme 3.5.0
Looks like that's because you don't use <span/> directly in JSX returned from render method but assign it to a variable and then add that variable to JSX.
If you execute console.log(wrapperOne.debug()) you will see the following result (I've removed styles and components that you didn't provided):
<nav className="PageNav">
<span className="chevron" />
<ul className="nav-links">
<li className="nav-item active">
<Component className="active-nav-option">
<a title={[undefined]}>
<img src="/static/img/nav-icons/0.svg" alt="An icon for the 0 page, undefined" />
</a>
</Component>
</li>
</ul>
</nav>
As you can see, you have <Component className="active-nav-option"> instead of <span className="active-nav-option"> thus span.active-nav-option can't find anything.
could you please tell me how to implement tabs in react with using any library ?
I follow this link and tried to make tabs
https://toddmotto.com/creating-a-tabs-component-with-react/
but not succeeded.
Here is my code:
https://codesandbox.io/s/D9Q6qWPEn
import React, {Component} from 'react';
class Tabs extends Component {
constructor() {
super();
this.state= {
selected:0
}
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
render() {
return (
<div className="tabs">
{this._renderContent()}
</div>
)
}
}
export default Tabs;
I am not able to show tabs and it there individual contents after click
any update ?
If I understand your problem correctly, you just want to display labels for your tabs.
You can do this in many ways in React but without changing your code too much, you can accomplish this task by changing render() method of Tabs component, like below:
setTab(index) {
this.setState({
selected: index,
});
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
What we do here is basically rendering labels based on props.children. For each label we append click handler.
You could improve this code by creating specific components like TabPane, TabLabel.
Full code of Tabs component.
import React, { Component } from "react";
class Tabs extends Component {
constructor() {
super();
this.state = {
selected: 0
};
}
setTab(index) {
this.setState({
selected: index,
});
}
_renderContent() {
return (
<div className="tabs__content">
{this.props.children[this.state.selected]}
</div>
);
}
_renderLabels() {
return this.props.children.map((child, index) => (
<div key={child.props.label} onClick={() => { this.setTab(index) }}>
{child.props.label}
</div>
));
}
render() {
return (
<div className="tabs">
{this._renderLabels()}
{this._renderContent()}
</div>
);
}
}
export default Tabs;
Demo: https://codesandbox.io/s/pRvG6K0r
I implemented this and is working awesome:
I'm using a functional component with Hooks.
const Tabs = () => {
const [currentTab, setCurrentTab] = useState('first-tab');
const toggleTab = () => {
switch (currentTab) {
case 'first-tab':
return <FirstTab />;
case 'second-tab':
return <SecondTab />;
default:
return null;
}
};
return (
<div>
<div>
<h3 onClick={() => setCurrentTab('first-tab')}>First Tab</h3>
<h3 onClick={() => setCurrentTab('second-tab')}>Second Tab</h3>
</div>
{toggleTab()}
</div>
);
};
You can add more components if you want.
Psdt: And if you want to add styles to the Current Tab Title you can create another useState Hook, something like "isActive" and this set it up on the tag classes.
const Tabs = () => {
const tabsData = [
{ id: 0, tabName: "first tab", tabData: "first tab data" },
{ id: 1, tabName: "second tab", tabData: "second tab data" },
{ id: 2, tabName: "third tab", tabData: "third tab data" },
];
const [activeIndex, setActiveIndex] = useState(false);
const tabClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ul>
{tabsData.map((tab) => {
return (
<li key={tab.id} onClick={() => tabClick(tab.id)}>
{tab.tabName}
</li>
);
})}
</ul>
<ul>
{tabsData.map((tab) => {
if (tab.id === activeIndex) {
return <li key={tab.id}>{tab.tabData}</li>;
}
})}
</ul>
</>
);
};
if all you have is a hammer, everything looks like a nail
Don't try to use React for everything. Not only is this a bad practice. React was never intended to fit that purpose. Creating tabs is 90% CSS and only 10% JS (controlling which one gets the "active" state.
I created an example that implements tabs 100% using CSS, It doesn't work well since only after a tab is clicked the content is shown, there is no default.
If you wanted to automate this (e.g.) in an React component The only thing you have to do is bind a tab click to an active state a classname representing the active state (e.g.: tab--active). And create a default.
.tabs__list-item {
display: inline;
}
.tab {
display: none;
}
.tab:target { /* Only works if tab is clicked, can be fixed with React. */
display: block;
}
<nav class="tabs">
<ul class="tabs__list">
<li class="tabs__list-item"><a class="tabs__link" href="#tab1">Tab 1</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab2">Tab 2</a></li>
<li class="tabs__list-item"><a class="tabs__link" href="#tab3">Tab 3</a></li>
</ul>
</nav>
<section class="tab" id="tab1">
<h1>Tab 1 header</h1>
<p>This is the content of tab 1.</p>
</section>
<section class="tab" id="tab2">
<h1>Tab 2 header</h1>
<p>This is the content of tab 2.</p>
</section>
<section class="tab" id="tab3">
<h1>Tab 3 header</h1>
<p>This is the content of tab 3.</p>
</section>
In short:
Write the HTML structure / CSS markup first
Observe missing logic
Implement missing logic using the best tool.
React may be a valid tool for this purpose but start by having React rendering your HTML and CSS. If you can't get the "wiring" logic to work open a question indicating what specific problem you encounter.