react-bootstrap breadcrumb with react-router-dom - reactjs

I'm trying to use react-bootstrap breadcrumb as below.
<Breadcrumb>
<Breadcrumb.Item href="#">Home</Breadcrumb.Item>
<Breadcrumb.Item><Link to={"/products"}>Products</Link></Breadcrumb.Item>
<Breadcrumb.Item active>{productName}</Breadcrumb.Item>
</Breadcrumb>
As you can expect, products Link will render anchor tag inside another anchor tag, which is invalid markup. But Home creates a simple anchor tag instead of react's Link making the page to reload, making it unusable.
What's the solution for this? Unfortunately, there's no mention of this in react-bootstrap doc. (link)

I would probably use react-router-bootstrap, but if you don't want to include it as a dependency, you can apply the link by hand using the now available linkAs and linkProps Breadcrumb params. For instance:
<Breadcrumb.Item linkAs={Link} linkProps={{ to: "/path" }}>
My item
</Breadcrumb.Item>
This approach is interesting especially if you are using just the "as" attribute with other components like Button or NavLink.

This nicely works and it does not refresh the whole page, only what's needed to change
import { Breadcrumb } from "react-bootstrap";
import { Link } from "react-router-dom";
export const SiteMap = ({ hrefIn }) => {
const items = [
{ href: "/dictionaries", name: "Dictionaries" },
{ href: "/antonyms", name: "Antonyms" },
];
return (
<Breadcrumb>
{items.map((item) =>
item.href === hrefIn ? (
<Breadcrumb.Item active>{item.name}</Breadcrumb.Item>
) : (
<Breadcrumb.Item linkProps={{ to: item.href }} linkAs={Link}>
{item.name}
</Breadcrumb.Item>
)
)}
</Breadcrumb>
);
};

I ended up dropping react-boostrap and doing it 'by hand':
const Breadcrumbs = ({ breadcrumbs }) => (
<ol className="breadcrumb">
{breadcrumbs.map((breadcrumb, index) => (
<li key={breadcrumb.key}>
<NavLink to={breadcrumb.props.match.url}>
{breadcrumb}
</NavLink>
</li>
))}
</ol>
);

It works for me if I wrap <Breadcrumb.Item> into the <LinkContainer>.
Now outer Breadcrumb "My applications" which points to /applications URL redirects my app with react-router to the applications page.
I tested this with react-bootstrap v0.32.4 https://5c507d49471426000887a6a7--react-bootstrap.netlify.com/components/breadcrumb/
I got <LinkContainer> from react-router-bootstrap package: https://github.com/react-bootstrap/react-router-bootstrap
I saw "the wrapping" here before, though I don't generate breadcrumb in a loop: https://github.com/react-bootstrap/react-router-bootstrap/issues/141#issue-122688732
import { LinkContainer } from 'react-router-bootstrap';
// and then in the render function
<Breadcrumb>
<LinkContainer to="/applications" exact>
<Breadcrumb.Item>My applications</Breadcrumb.Item>
</LinkContainer>
<Breadcrumb.Item active>My First Applicaton</Breadcrumb.Item>
</Breadcrumb>

Here I have passed onClick to Breadcrumb.Item to handle navigation with the help of
import { withRouter } from "react-router-dom";
Here is the sample:
const RenderBreadcrumb = ({ breadcrumbInfo, history }) => {
const handleRedirect = (url) => {
history.push(url);
}
return (
<Breadcrumb>
{ map(breadcrumbInfo, (item) => {
if(item.isActive) {
return (<Breadcrumb.Item active>{item.text}</Breadcrumb.Item>);
}
return (<Breadcrumb.Item onClick={() => { handleRedirect(item.link); }}>{item.text}</Breadcrumb.Item>);
})}
</Breadcrumb>
)
}
export default withRouter(RenderBreadcrumb);

Bootstrap.Item internally uses SafeAnchor which allows you to not use a link if you don't want to.
Using the as prop you can modify what tag is used (a by default). For example you can pass:
<Bootstrap.Item as="div" />
And it will use a div tag for presenting the item.

Related

In Gatsby how to render React Icons dynamically from gatsby-config's menuLinks?

Following Gatsby's doc on Creating Dynamic Navigation in Gatsby I created a barebones menu and wanted to see if I can add React Icons' components to it:
gatsby-config.js (stripped down)
menuLinks:[
{
name:'home',
icon: 'AiFillHome',
link:'/'
},
{
name:'contact',
icon: 'AiFillHome',
link:'/contact'
}
]
after finding out that Gatsby errors out when I tried creating an external menuLinks file as a module, example:
failed approach as module:
import React from 'react'
// React Icons
import { AiFillHome } from 'react-icons/ai'
const Menu = [
{
name:'home',
icon: <AiFillHome />,
link:'/'
},
{
name:'contact',
icon: <AiFillHome />,
link:'/contact'
}
]
export default Menu
I dont have an issue in my query:
const data = useStaticQuery(
graphql`
query {
site {
siteMetadata {
menuLinks {
name
icon
link
}
}
}
}
`,
)
in a file I've passed down menu props to from my query and then map.
(stripped down file):
{menu.map((menuItem, key) => {
return (
<Link key={key} to={menuItem.link}>
<div>
<span className="icon">{`<${menuItem.icon} />`}</span>
{menuItem.name}
</div>
</Link>
)
})}
my icon doesn't render. I've also tried:
<span className="icon" component={menuItem.icon} />
doesn't work. I've also tried:
<span className="icon"><menuItem.icon /></span>
and:
<span className="icon">{React.createElement(menuItem.icon)}</span>
Research:
Making an HTML string from a React component in background, how to use the string by dangerouslySetInnerHTML in another React component
React render components from string
How to render string as custom React component?
React / JSX Dynamic Component Name
In Gatsby how can I pass an icon's component name to menuLinks and later render it?
Edit
After the answer the implementation of:
{menu.map(({link, name, icon: Icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><Icon /></span>
{name}
</div>
</Link>
)
})}
the browser doesn't render the React Icon Component and when examining the Elements panel I get:
<span class="icon">
<aifillhome></aifillhome>
</span>
Also note I do get a terminal error of:
warning 'AiFillHome' is defined but never used no-unused-vars
which was expected but that leads to me wondering how do I bring in:
import { AiFillHome } from 'react-icons/ai'
Try something like this:
{menu.map(({link, name, icon: Icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><Icon /></span>
{name}
</div>
</Link>
)
})}
Since you are looping through the menu elements, icon is not interpreted as a React element because of the capitalization. In the destructuring above you are parsing icon as Icon so rendering it as a React component in <Icon /> should do the trick because it's the element itself.
There's another workaround that seems a little bit overkill but will work: you can always use a Map to hold the asset component, something like:
const AssetComponentsTuple = new Map([
[`home`, <AiFillHome />],
[`otherAsset`, <AiFillHome2 />],
]);
export default AssetComponentsTuple;
Your menu will become:
const Menu = [
{
name:'home',
icon: 'home',
link:'/'
},
{
name:'contact',
icon: 'otherAsset',
link:'/contact'
}
]
Then in the loop:
{menu.map(({link, name, icon}) => {
let IconComponent = AssetComponentsTuple.get(icon);
return (
<Link key={name} to={link}>
<div>
<span className="icon"><IconComponent /></span>
{name}
</div>
</Link>
)
})}
You can even get rid of icon property and use directly the name in the AssetComponentsTuple, in that case: AssetComponentsTuple.get(name)
Implementation of:
const AssetComponentsTuple = new Map([
[`home`, <AiFillHome />],
[`otherAsset`, <AiFillHome2 />],
])
export default AssetComponentsTuple
would throw an error of:
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
but the approach gave me an idea for this workaround of:
{menu.map(({link, name, icon}) => {
return (
<Link key={name} to={link}>
<div>
<span className="icon"><IconRetrieve icon={icon} /></span>
{name}
</div>
</Link>
)
})}
and that allowed me to pass the icon string and ternary for the component.
IconRetrieve component:
import React from 'react'
import PropTypes from 'prop-types'
// React Icons
import { AiFillHome } from 'react-icons/ai'
import { MdCall } from 'react-icons/md'
import { BiErrorCircle } from 'react-icons/bi'
const IconRetrieve = ({ icon }) => {
const mapIcons = new Map([
[`home`, <AiFillHome />],
[`contact`, <MdCall />],
[`default`, <BiErrorCircle />],
])
return mapIcons.has(icon) ? mapIcons.get(icon) : mapIcons.get('default')
}
IconRetrieve.propTypes = {
icon: PropTypes.string.isRequired,
}
export default IconRetrieve
The positive of this approach is I can create SVG components or any component in that matter and return it based on the icon string.

Conditional render Navigation

Hi I need conditional render my navigation. I use Gatsby and GraphQl. I have Navigation Component and depends on route in which it is I need render different Navigation. The problem is I can not make conditional useStateStatic hook. I've made two diffrent Navigation in my source it is DatoCMS,but I can not query for it.
const Navigation = () => {
const pathName = window.location.pathname;
const data = useStaticQuery(
graphql`
{
datoCmsNavigation(sectionId: { eq: "navigation" }) {
sectionId
logo {
url
alt
}
menuItem {
id
name
href
}
}
}
`
);
const {
datoCmsNavigation: { sectionId, logo, menuItem },
} = data;
return (
<NavBar id={sectionId}>
<NavBarLogoWrapper>
<a href="/">
<img src={logo.url} alt={logo.test} />
</a>
</NavBarLogoWrapper>
<NavBarList>
{menuItem.map((item, index) => (
<NavBarItem key={index}>
<a href={item.href}> {item.name.toLowerCase()}</a>
</NavBarItem>
))}
</NavBarList>
</NavBar>
);
};
Here is my Navigation component. Does anyone has Idea how can I deal with it ?
Your approach will fail in gatsby build since window (and other global objects) are not available during the SSR. You don't need a useState hook in your scenario, you just need to know which page is actually the user seeing.
Top-level components (pages) in Gatsby own the location property, which allows you to know a bunch of data, including the current page. You can pass that data to your Navigation component in order to make your comparison. For example:
const Blog = ({location, someOtherDestructuredProps}) => {
return <section>
<Navigation currentPage={location.pathname}/>
<OtherComponent />
</section>
}
Then, in your Navigation component:
const Navigation = ({currentPage ="/"}) => {
const pathName = window.location.pathname;
const data = useStaticQuery(
graphql`
{
datoCmsNavigation(sectionId: { eq: "navigation" }) {
sectionId
logo {
url
alt
}
menuItem {
id
name
href
}
}
}
`
);
const {
datoCmsNavigation: { sectionId, logo, menuItem },
} = data;
return (
<NavBar id={sectionId}>
<NavBarLogoWrapper>
<a href="/">
<img src={logo.url} alt={logo.test} />
</a>
</NavBarLogoWrapper>
<NavBarList>
{menuItem.map((item, index) => {
if(currentPage == '/blog') return <DifferentNavBar />
return <NavBarItem key={index}>
<a href={item.href}> {item.name.toLowerCase()}</a>
</NavBarItem>
})}
</NavBarList>
</NavBar>
);
};
Among minor changes, the important part is to set a default value for the currentPage (in case it's missing) and the change of the map loop in order to return different navigation bars. Of course, tweak it to adapt it to your needs and requeriments.

react-router with context - why doesn't it work the same with anchor vs Link

I have the below sample code using react-router and context hooks where I am trying to understand why it behaves differently when I use anchors instead of Link components. The Link components are commented out.
This app just simply displays a screen with an html link where you can click it to display component 2 (component 1 is displayed initially). I am updating the context value in the onClick event for the anchor (I use the setName function to update the name in the context).
When I use anchor tags, it doesn't keep the context value that was updated. So when it goes to component2, the name value in the context displays as person1. However, if I comment out the anchors and use the Link components instead, the context value is updated properly.
Why do the Link components work as expected but not the anchors when updating context?
import React, { useContext, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
const NameContext = React.createContext();
function App() {
const [name, setName] = useState('name1');
return (
<NameContext.Provider value={{ name, setName }}>
<Router>
<Route exact path="/" component={Component1} />
<Route exact path="/component1" component={Component1} />
<Route exact path="/component2" component={Component2} />
</Router>
</NameContext.Provider>
);
}
function Component1() {
const { name, setName } = useContext(NameContext);
const history = useHistory();
return (
<>
<div>This is component 1, name = {name}</div>
<a href="/component2" onClick={() => setName('name2')}>
Click to display component 2
</a>
{/* <Link
onClick={() => setName('name2')}
to={(location) => {
return { ...location, pathname: '/component2' };
}}
>
Click to display component 2
</Link> */}
</>
);
}
function Component2() {
const { name, setName } = useContext(NameContext);
const history = useHistory();
return (
<>
<div>This is component 2, name = {name}</div>
<a href="/component1" onClick={() => setName('name3')}>
Click to display component 1
</a>
{/* <Link
onClick={() => setName('name3')}
to={(location) => {
return { ...location, pathname: '/component1' };
}}
>
Click to display component 1
</Link> */}
</>
);
}
export default App;
An anchor tag reloads the browser by default. If you want to avoid this default behavior you can call the preventDefault method on the onClick event.
react-router doesn't use anchor tags either, so if you want to use anchor tags you have to manually update the history.
<div>This is component 1, name = {name}</div>
<a
href="/component2"
onClick={(e) => {
e.preventDefault();
setName("name2");
history.push("/component2");
}}
>
Click to display component 2
</a>

React & Material-UI Pagination - Hooking up Material-UI's component to React-Router

I have a component that handles pagination for real estate listings pulled in from an API. It's successfully displaying the appropriate amount of listings and is handling creating the appropriate amount of links.
It looks like this:
const Paginations = ({ listingsPerPage, totalListings, paginate }) => {
const pageNumbers = [];
for(let i = 1; i <= Math.ceil(totalListings / listingsPerPage); i++) {
pageNumbers.push(i);
}
return (
<ul>
{pageNumbers.map(number => (
<li key={number}>
<a onClick={() => paginate(number)} href="#">
{number}
</a>
</li>
))}
</ul>
);
}
export default Paginations;
I would like to replace the basic ul with Material-UI's Pagination component. I would also like to hook it up to React-Router and for it to handle routing to the appropriate page i.e. the component's URL is /search/, if you click the the 3 it would take you to /search?page=3.
The example Material-UI gives looks like this:
import React from 'react';
import { MemoryRouter, Route } from 'react-router';
import { Link } from 'react-router-dom';
import Pagination from '#material-ui/lab/Pagination';
import PaginationItem from '#material-ui/lab/PaginationItem';
export default function PaginationLink() {
return (
<MemoryRouter initialEntries={['/inbox']} initialIndex={0}>
<Route>
{({ location }) => {
const query = new URLSearchParams(location.search);
const page = parseInt(query.get('page') || '1', 10);
return (
<Pagination
page={page}
count={10}
renderItem={(item) => (
<PaginationItem
component={Link}
to={`/inbox${item.page === 1 ? '' : `?page=${item.page}`}`}
{...item}
/>
)}
/>
);
}}
</Route>
</MemoryRouter>
);
}
How can I incorporate the example with the router into my Paginations component?
https://codesandbox.io/s/material-demo-forked-uhczc?file=/demo.js
please find the above link, which will help you to create the material ui router pagination.
Try changing the url ex: https://....../5 (the pagination will be set to 5)

React Parent Component Not Rendering Child Component

Basic question, but we all start somewhere.
I have a parent Nav.js
import React, { Component } from 'react'
import navLink from './navLink'
class Nav extends Component {
render () {
return (
<div>
<nav className="bb bt b--black-20 tc center bg-white">
<navLink />
</nav>
</div>
)
}
}
Nav.displayName = 'Nav'
export default Nav
Here's the file that won't get displayed:
navLink.js:
import React from 'react'
import Link from 'next/link'
const navigationLinks = [
{ name: '1', link: '/', router: true },
{ name: '2', link: '#' }
]
const renderLink = (link, i) => {
if (link.router === true) {
return (
<Link href={link.link}>
<a className="yellow" key={i}>{link.name}</a>
</Link>
)
} else {
return (
<a className="red" key={i} target="_blank" rel="noopener noreferrer" href={link.link}>{link.name}</a>
)
}
}
const navLink = () => (
<nav className="container">
{navigationLinks.map(link => renderLink(link))}
</nav>
)
navLink.displayName = 'navLink'
export default navLink
I'm my problem is super basic, but I have no idea how to resolve this. Thanks for your time!
The main problem is navLink with a small letter, it should be NavLink as RenderLink if you want to use it.
In JSX lower case are considered to be a HTML tags, HTML is not have no a navLink tag so it's just didn't displayed.
User-Defined Components Must Be Capitalized
The first part of a JSX tag determines the type of the React element.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX expression, Foo must be in scope.
see: docs
Render links
const SimpleLink = ({link}) => (
<Link href={link.link}>
<a className="yellow">{link.name}</a>
</Link>
);
const RenderRouter = ({link}) => (
<a className="red" target="_blank" href={link.link}>{link.name}</a>
);
const renderLink = link => link.router ?
( <SimpleLink key={i} link={link}/> ) :
( <RenderRouter key={i} link={link}/> );
...
const NavLink = () => (
<div className="container">
{ navigationLinks.map((link, i) => renderLink(link, i))}
</div>
);
...
export default NavLink
Import
import NavLink from './navLink.js'
Nav Component
class Nav extends Component {
render () {
return (
<nav className="bb bt b--black-20 tc center bg-white">
<NavLink />
</nav>
)
}
}
See: working example
Fix proptypes
react-router may provide propType definitions for it already, but whether they do or not, you'd still need to explicitly connect them.
Rend.propTypes = {
link: React.PropTypes.shape({
link: React.PropTypes.string.isRequired
}),
};
See: eslint-plugin-react/issues/1109

Resources