GatsbyJS: Change menu link when switching between pages - reactjs

I have a simple Gatsby site using two languages. In the header component, there is a link to the second language, when clicking on it, that language becomes the selected one and the other must appear as an option. Everything works but does not load these changes without refreshing the page. What am I doing wrong? Here is my code:
import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
import AnchorLink from "react-anchor-link-smooth-scroll"
import Logo from "../images/logo.svg"
const path = window.location.pathname;
const Header = ({ siteTitle }) => {
return (
<>
<header>
<div className="container">
<div className="nav-links">
<Link to="/" className="logo">
<Logo />
</Link>
<div className="wrapper">
<AnchorLink href="#contact" className="nav-item contact">
Contact us
</AnchorLink>
</div>
<AnchorLink className="button button--sm" href="#contact">
Contact
</AnchorLink>
{path === '/' && (
<Link to='/de' className="nav-item">
DE
</Link>
)}
{path === '/de' && (
<Link to='/' className="nav-item">
EN
</Link>
)}
</div>
</div>
</header>
</>
)
}
Header.propTypes = {
siteTitle: PropTypes.string,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header

I believe you should put the path variable inside the Header component so that it is re-evaluated on each render.
const Header = ({ siteTitle }) => {
const path = window.location.pathname;
return (
<>
<header>

Related

ReactJs - rendering problem. why my signout button show up after refresh?

can somebody help me. i just wanna know, after i get the data user from localstorage. the signout button will show up after i refresh the page. can somebody help me pls? thank you
here's the code :
import React, { useEffect, useState } from 'react';
import './header.css';
import { Link } from 'react-router-dom';
const Header = () => {
const [token, setToken] = useState(
localStorage.getItem('user')
? JSON.parse(localStorage.getItem('user')).token
: ''
);
const signOuthandler = (e) => {
localStorage.removeItem('user');
setToken();
};
return (
<nav>
<ul>
{!token && (
<>
<li>
<Link to="/login">
<button>Sing in</button>
</Link>
</li>
<li>
<Link to="/register">
<button>Register</button>
</Link>
</li>
</>
)}
{token && (
<>
<li>
<Link onClick={signOuthandler} to="/login">
<button>Sing Out</button>
</Link>
</li>
</>
)}
</ul>
</nav>
);
};
export default Header;
update your sign out token in 'useEffect',
useEffect(()=>{
setToken(localStorage.getItem('user')
? JSON.parse(localStorage.getItem('user')).token
: '')
},[localStorage.getItem('user')])

How to close sidenav when I click anywhere of the page in react js

**Nav-header.js**
The sidebar should not collapse when I am clicking outside of the page in react js. Please help with that. this is the code I wrote for the side menu. I am a beginner at the react js. Please help
import React, { useState } from 'react'
import * as FaIcons from 'react-icons/fa'
import * as AiIcons from 'react-icons/ai'
import { Link } from 'react-router-dom'
import { SidebarData } from './SidebarData'
import './Navbar.css'
import { IconContext } from 'react-icons'
const Header = () => {
const [sidebar, setsidebar] = useState(false)
const showSideBar = () => setsidebar(!sidebar)
return (
<>
<IconContext.Provider value={{ color: '#1D1D1D' }}>
<div className='navbar'>
<Link to="#" className='menu-bars'>
<FaIcons.FaBars onClick={showSideBar} color="#009540"/>
</Link>
<span className="meny">Meny</span>
</div>
<nav className={sidebar ? 'nav-menu active' : 'nav-menu' } >
<ul className='nav-menu-items' onClick={showSideBar}>
<li className='navbar-toggle'>
<Link to="#" className='menu-bars-logo'>
<AiIcons.AiOutlineClose/>
</Link>
</li>
{SidebarData.map((item, index) => {
return (
<li key={index} className={item.cName}>
<Link to={item.path}>
<span>{item.title}</span>
</Link>
</li>
)
})}
</ul>
</nav>
</IconContext.Provider>
</>
)
}
export default Header
You can use onblur, in react it would look like
return (
<div onBlur={() => {
... do something like setState
}}>
)

How to show/hide menu items in next.js if user authenticated

I want to for example only show the logout button if a user is actually logged in.
My _app.js file:
import React from "react";
import App from "next/app";
import Navbar from "../components/navbar";
import Layout from "../components/layouts/mainLayout";
import { handleAuthSSR } from "../utils/auth";
export default class MyApp extends App {
render() {
const { Component, pageProps, userAuth } = this.props;
return (
<Layout userAuth={pageProps.token}>
<Component {...pageProps} />
</Layout>
);
}
}
Main Layout:
import Head from "next/head";
import Navbar from "../navbar";
const Layout = props => {
return (
<>
<Head>
<title>My App</title>
<link
rel="stylesheet"
href="https://bootswatch.com/4/cerulean/bootstrap.min.css"
></link>
<link href="/static/css/styles.css" rel="stylesheet"></link>
</Head>
<Navbar />
<div className="container">{props.children}</div>
</>
);
};
export default Layout;
Navbar:
import Link from "next/link";
import { logout } from "../utils/auth";
const Navbar = props => (
<nav className="navbar navbar-expand navbar-dark bg-dark mb-4">
<div className="container">
<a className="navbar-brand" href="#">
My App
</a>
<div className="collapse navbar-collapse">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link href="/">
<a className="nav-link">Home</a>
</Link>
</li>
<li className="nav-item">
<Link href="/about">
<a className="nav-link">About</a>
</Link>
</li>
<li className="nav-item">
<Link href="/register">
<a className="nav-link">Register</a>
</Link>
</li>
{props.userAuth ? (
<li>
<button className="btn btn-primary" onClick={logout}>
Logout
</button>
</li>
) : (
<li className="nav-item">
<Link href="/login">
<a className="nav-link">Login</a>
</Link>
</li>
)}
</ul>
</div>
</div>
</nav>
);
export default Navbar;
So, I am passing userAuth via props but I haven't actually got the logic for userAuth.
The handleAuthSSR looks like:
export const handleAuthSSR = ctx => {
const { token } = nextCookie(ctx);
if (ctx.req && !token) {
ctx.res.writeHead(302, { Location: "/login" });
ctx.res.end();
return;
}
if (!token) {
console.log("no token found");
Router.push("/login");
}
return token;
};
I am using this as part of a HOC when a user navigates around and it works but not sure how to get this to work with regards to the navigation show/hide.
I can see that you didn't pass the userAuth prop to the navbar component in mainLayout.js. Also you need to work on your Parent/Children props communications.
Maybe you can use your HOC to return token or false (or null instead of false). Use it then in navbar.js to toggle Login/Logout link.

How to call props of a sidebar Component? - Leads to TypeError: Cannot read property 'map' of undefined

I am adding a props of sidebar Component to my template.
I am passing {...this.props} to Sidebar.
But it still leads to TypeError: Cannot read property 'map' of undefined in my Menu file.
My PostTemplateDetails file that I wish to add the Sidebar component:
import React from 'react'
import Sidebar from '../Sidebar'
import { Link } from 'gatsby'
import moment from 'moment'
import './style.scss'
class PostTemplateDetails extends React.Component {
render() {
const { subtitle, author } = this.props.data.site.siteMetadata
const post = this.props.data.markdownRemark
const tags = post.fields.tagSlugs
const tagsBlock = (
<div className="post-single__tags">
<ul className="post-single__tags-list">
{tags &&
tags.map((tag, i) => (
<li className="post-single__tags-list-item" key={tag}>
<Link to={tag} className="post-single__tags-list-item-link">
{post.frontmatter.tags[i]}
</Link>
</li>
))}
</ul>
</div>
)
return (
<div>
<Sidebar {...this.props} />
<div className="content">
<div className="content__inner">
<div className="post-single">
<div className="post-single__inner">
<h1 className="post-single__title">{post.frontmatter.title}</h1>
<div
className="post-single__body"
/* eslint-disable-next-line react/no-danger */
dangerouslySetInnerHTML={{ __html: post.html }}
/>
<div className="post-single__date">
<em>
Published {moment(post.frontmatter.date).format('D MMM YYYY')}
</em>
</div>
</div>
<div className="post-single__footer">
{tagsBlock}
<hr />
<p className="post-single__footer-text">
{subtitle}
<a
href={`https://twitter.com/${author.twitter}`}
target="_blank"
rel="noopener noreferrer"
>
<br /> <strong>{author.name}</strong> on Twitter
</a>
</p>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default PostTemplateDetails
My Sidebar component file:
import React from 'react'
import get from 'lodash/get'
import { Link } from 'gatsby'
import Menu from '../Menu'
import Links from '../Links'
import profilePic from '../../pages/photo.jpg'
import './style.scss'
class Sidebar extends React.Component {
render() {
const { location } = this.props
const {
author,
subtitle,
copyright,
menu,
} = this.props.data.site.siteMetadata
const isHomePage = get(location, 'pathname', '/') === '/'
/* eslint-disable jsx-a11y/img-redundant-alt */
const authorBlock = (
<div>
<Link to="/">
<img
src={profilePic}
className="sidebar__author-photo"
width="75"
height="75"
alt={author.name}
/>
</Link>
{isHomePage ? (
<h1 className="sidebar__author-title">
<Link className="sidebar__author-title-link" to="/">
{author.name}
</Link>
</h1>
) : (
<h2 className="sidebar__author-title">
<Link className="sidebar__author-title-link" to="/">
{author.name}
</Link>
</h2>
)}
<p className="sidebar__author-subtitle">{subtitle}</p>
</div>
)
/* eslint-enable jsx-a11y/img-redundant-alt */
return (
<div className="sidebar">
<div className="sidebar__inner">
<div className="sidebar__author">{authorBlock}</div>
<div>
<Menu data={menu} />
<Links data={author} />
<p className="sidebar__copyright">{copyright}</p>
</div>
</div>
</div>
)
}
}
export default Sidebar
My Menu component file, which is added in the Sidebar component file - this is where the error seems to be residing.
import React from 'react'
import { Link } from 'gatsby'
import './style.scss'
class Menu extends React.Component {
render() {
const menu = this.props.data
const menuBlock = (
<ul className="menu__list">
{menu.map(item => (
<li className="menu__list-item" key={item.path}>
<Link
to={item.path}
className="menu__list-item-link"
activeClassName="menu__list-item-link menu__list-item-link--active"
>
{item.label}
</Link>
</li>
))}
</ul>
)
return <nav className="menu">{menuBlock}</nav>
}
}
export default Menu
I am not sure why this is not working, since adding in my PAGETemplateDetails file seem to be working fine:
import React from 'react'
import Sidebar from '../Sidebar'
import './style.scss'
class PageTemplateDetails extends React.Component {
render() {
const page = this.props.data.markdownRemark
return (
<div>
<Sidebar {...this.props} />
<div className="content">
<div className="content__inner">
<div className="page">
<h1 className="page__title">{page.frontmatter.title}</h1>
<div
className="page__body"
/* eslint-disable-next-line react/no-danger */
dangerouslySetInnerHTML={{ __html: page.html }}
/>
</div>
</div>
</div>
</div>
)
}
}
export default PageTemplateDetails
SiteMetadata.menu is queried on the Post template File:
import React from 'react'
import Helmet from 'react-helmet'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import PostTemplateDetails from '../components/PostTemplateDetails'
class PostTemplate extends React.Component {
render() {
const { title, subtitle } = this.props.data.site.siteMetadata
const post = this.props.data.markdownRemark
const { title: postTitle, description: postDescription } = post.frontmatter
const description = postDescription !== null ? postDescription : subtitle
return (
<Layout>
<div>
<Helmet>
<title>{`${postTitle} - ${title}`}</title>
<meta name="description" content={description} />
</Helmet>
<PostTemplateDetails {...this.props} />
</div>
</Layout>
)
}
}
export default PostTemplate
export const pageQuery = graphql`
query PostBySlug($slug: String!) {
site {
siteMetadata {
title
subtitle
copyright
author {
name
twitter
}
disqusShortname
url
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
fields {
tagSlugs
}
frontmatter {
title
tags
date
description
}
}
}
`
Not sure if this is relevant but this is the post file:
import React from 'react'
import { Link } from 'gatsby'
import moment from 'moment'
import './style.scss'
class Post extends React.Component {
render() {
const {
title,
date,
category,
description,
} = this.props.data.node.frontmatter
const { slug, categorySlug } = this.props.data.node.fields
return (
<div className="post">
<div className="post__meta">
<time
className="post__meta-time"
dateTime={moment(date).format('MMMM D, YYYY')}
>
{moment(date).format('MMMM YYYY')}
</time>
<span className="post__meta-divider" />
<span className="post__meta-category" key={categorySlug}>
<Link to={categorySlug} className="post__meta-category-link">
{category}
</Link>
</span>
</div>
<h2 className="post__title">
<Link className="post__title-link" to={slug}>
{title}
</Link>
</h2>
<p className="post__description">{description}</p>
<Link className="post__readmore" to={slug}>
Read
</Link>
</div>
)
}
}
export default Post
You're passing the property data as "menu" <Menu data={menu} />
In the Menu component, you don't have the menu property, you have this.props.data, which is equal to menu value, as defined in the Sidebar component. Probably there's no such property "menu" on this.props.data
So your code should be const data = this.props or const menu = this.props.data if you want to keep the variable name.
Thank you everyone, it was a query problem as ksav suspected. The Menu and Sidebar components were fine. I failed to query the menu information in my sitemetadata, and was able to fix it by going to my Post Template File and querying the menu, like so:
export const pageQuery = graphql`
query PostBySlug($slug: String!) {
site {
siteMetadata {
title
subtitle
copyright
menu {
label
path
}
author {
name
twitter
}
disqusShortname
url
}
}
markdownRemark(fields: { slug: { eq: $slug } }) {
id
html
fields {
tagSlugs
}
frontmatter {
title
tags
date
description
}
}
}
`
Thank you everyone, I'm struggling but I got there.

React Router v4 setting activeClass on parent

Not too familiar with react router, but I need the functionality of the NavLink to set the active class on the parent li element, and not the a element.
To implement this I just looked at the source code of the NavLink and copied it to a new element. (Example using typescript, but just about the same as js anyway)
import * as React from 'react';
import { Link, withRouter, Route } from 'react-router-dom';
class LiNavLink extends React.Component<any, {}> {
render() {
const {to,exact, strict, activeClassName, className, activeStyle, style, isActive: getIsActive, ...rest } = this.props;
return (
<Route
path={typeof to === 'object' ? to.pathname : to}
exact={exact}
strict={strict}
children={({ location, match }) => {
const isActive = !!(getIsActive ? getIsActive(match, location) : match)
return (
<li
className={isActive ? [activeClassName, className].join(' ') : className}
style={isActive ? { ...style, ...activeStyle } : style}>
<Link
to={to}
{...rest}
/>
</li>
)
}}
/>
);
}
}
export default LiNavLink;
Then the usage:
<ul>
<LiNavLink activeClassName='active' exact={true} strict to="/example"><span>Active</span></LiNavLink>
<LiNavLink activeClassName='active' exact={true} strict to="/example/archived"><span>Archived</span></LiNavLink>
</ul>
I'm using a HashRouter and for some reason which I can't figure out, this does not update when the route changes, only when I hard 'refresh' the page does it update how it should.
I believe it is never updating because the props never change? So it doesn't know to update itself?
How can I get this to update? Or is my problem somewhere else?
In v4 after lots of tries I did.
Here my working code.
import React, { Component } from "react";
import logo from "../../logo.svg";
import { Link, withRouter } from "react-router-dom";
import PropTypes from "prop-types";
class Navbar extends Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
};
state = {};
getNavLinkClass = path => {
return this.props.location.pathname === path
? "nav-item active"
: "nav-item";
};
render() {
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<Link className="navbar-brand" to="/">
<img
src={logo}
width="30"
height="30"
className="d-inline-block align-top"
alt=""
/>
Utility
</Link>
<button
className="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon" />
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className={this.getNavLinkClass("/")}>
<Link className="nav-link" to="/">
Home
</Link>
</li>
<li className={this.getNavLinkClass("/age-counter")}>
<Link className="nav-link" to="/age-counter">
Age Counter
</Link>
</li>
</ul>
</div>
</nav>
);
}
}
export default withRouter(Navbar);
Demo working Code Sandbox
Check this one
class LiNavLink extends React.Component<NavLinkProps> {
render() {
return (
<Route exact={this.props.exact} path={this.props.to.toString()}>
{
({ match }) =>
<li className={match ? 'active' : undefined}>
<Link to={this.props.to} >
{this.props.children}
</Link>
</li>
}
</Route>
);
}
}
I am just starting with the react, so not sure if this is the best practices, but after going through router v4 docs, I used withRouter props -> location.pathname and compared it to my route.
Here is the Navigation.js:
import React from 'react';
import { withRouter } from 'react-router-dom';
import NavLink from '../General/NavLink';
const activeClass = (path, link) => {
if (path === link) {
return true;
}
return false;
};
const Navigation = props => {
const { location } = props;
return (
<ul className="menu menu--main nano-content">
<NavLink
to="/"
parentClass={
activeClass(location.pathname, '/')
? 'menu__item menu__item--active'
: 'menu__item'
}
linkClass="menu__link effect effect--waves"
>
Dashboard
</NavLink>
<NavLink
to="/users"
parentClass={
activeClass(location.pathname, '/users')
? 'menu__item menu__item--active'
: 'menu__item'
}
linkClass="menu__link effect effect--waves"
>
Users
</NavLink>
<NavLink
to="/projects"
parentClass={
activeClass(location.pathname, '/projects')
? 'menu__item menu__item--active'
: 'menu__item'
}
linkClass="menu__link effect effect--waves"
>
Projects
</NavLink>
<NavLink
href="http://google.com"
parentClass="menu__item"
linkClass="menu__link effect effect--waves"
>
Google
</NavLink>
</ul>
);
};
export default withRouter(Navigation);
From there you have parent and child classes that you can use on child component.
I found that by using CSS you can make the active link expand to fill up it's parent <li> element by setting display:block; in the active class.
For example if our link was:
<li>
<NavLink to="/overview" className=styles.sideLink activeClassName=styles.sideLinkSelected>
Overview
</NavLink>
</li>
then our CSS would be:
&__sideLinkSelected
{
background-color: blue;
display:block;
}

Resources