I'm trying to configure my Layout component to include a state for whether the sidebar is open/closed. The problem I'm having is figuring out the syntax format in order to get everything working. I'm not super well versed in ES6 (or React for that matter), which makes the guides I've found online quite confusing (with the various syntaxes). I'd look for general React help online, except for the fact that I haven't yet seen any examples with StaticQuery
, which I need to also integrate somehow (and is gatsby-specific).
Details are split up below. Thanks for any help!
Added the constructor and toggleMenu functions into the Component, and wrapped the StaticQuery in a return()
statement (to remove that error). Adding the functions blindly into the component results in a warning stating Read-only global 'constructor' should not be modified
and an error 'toggleMenu' is not defined
.
I've tried replacing the Component definition with class Layout extends React.Component{}
, but then I was unable to get the data and the children to pass through successfully.
I also saw an older example (I believe it was in an issue here) where someone was just defining state like state = {showMenu: True}
, which also doesn't work (errors claiming state isn't defined).
The component (without state):
import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import { StaticQuery, graphql } from 'gatsby'
import Sidebar from './sidebar'
import '../index.css'
import Menu from './../images/icons/icon-menu.svg'
const Layout = ({ children, data }) => {
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => (
<>
<Helmet
title={data.site.siteMetadata.title}
meta={[
{ name: 'description', content: 'Sample' },
{ name: 'keywords', content: 'sample, something' },
]}
bodyAttributes={{
class: "font-sans antialiased box-sizing text-black leading-tight min-h-screen"
}}
>
<html lang="en"/>
</Helmet>
<div className="flex min-h-screen">
<Sidebar siteTitle={data.site.siteMetadata.title}></Sidebar>
<Menu className="sm:hidden w-10 h-10"/>
<div className="w-full py-2 px-2 sm:px-0">
<main className="mt-4 max-w-md md:mx-auto leading-normal">
{children}
</main>
</div>
</div>
</>
)}
/>
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
What I essentially want to add:
constructor = (props) => {
super(props);
this.state.setState({ showMenu: true });
}
toggleMenu = () => {
this.state.showMenu.setState(!this.state.showMenu) //Flips true/false
}
The function toggleMenu
I would then call using onClick.
The correct syntax is:
constructor(props) {
super(props);
this.state = { showMenu: true };
}
toggleMenu() {
this.setState(prevState => { showMenu: !prevState.showMenu })
}
You also need to use the class
syntax, wrap your JSX in a render
function, and get children
and data
from this.props
:
class Layout extends React.Component {
// ... constructor and toggleMenu from above
render() {
const { children, data } = this.props;
return (
<StaticQuery ... />
)
}
}
If you want to know more about the how and why, I'd encourage you to look into the React tutorial. Of course, we are always happy to help as well!
Hi, I think it is somehow a related issue.
I don't get how to initialize a state using a Gatsby GraphQL Query.
I do it here:
https://github.com/baozi-technology/baozi-web/blob/master/src/components/Header/Header.jsx
But for some reason, the "img" sometimes doesn't load the content. It seems arbitrary as if from time to time the graphql query is done AFTER the component is loaded:
https://baozi.technology/
@NicoGim if you don't mind, i'm going to fork your repo and go over it and see if i can provide you with a answer, do you mind waiting a bit while i go over it?
Actually, I think I just solved the bug.
I was using the following query
query HeaderQuery {
allFile(filter: {name: {eq: "baozi-technology-full-logo"}, extension: {regex: "/jpg|gif/"}}) {
edges {
node {
relativePath
extension
}
}
}
}
Instead, I now use
query HeaderQuery {
allFile(filter: {name: {eq: "baozi-technology-full-logo"}, extension: {regex: "/jpg|gif/"}}) {
edges {
node {
publicURL
extension
}
}
}
}
I fill the "img" tag with publicURL instead of relativePath in the "src" parameter.
And now, it works fine.
@jonniebigodes thank you so much for your help!
The error had nothing to do with Gatsby itself though - sorry for the inconvenience.
@NicoGim glad that you managed to get it to work. I checked your code and based on that and if you don't mind me offering a couple of alternatives to your code that will achieve the same end result.
1- The first alternative with gatsby useStaticQuery
hook:
import React, { useState } from "react";
import { useStaticQuery, graphql, Link } from "gatsby";
import styles from "./Header.module.scss";
import Drawer from "../Drawer/Drawer";
const HeaderV1 = () => {
const [hover, setHover] = useState(false);
const logoResult = useStaticQuery(graphql`
{
mainLogo: file(
relativePath: { eq: "logos/baozi-technology-full-logo.jpg" }
) {
publicURL
}
gifLogo: file(
relativePath: { eq: "logos/baozi-technology-full-logo.gif" }
) {
publicURL
}
}
`);
// destructure the query result
const {mainLogo,gifLogo}= logoResult
return (
<header className={styles.header}>
<div className={styles.headerContainer}>
<div title="Jump to home page" className={styles.logoContainer}>
<Link to="/">
<img
onMouseOver={() => {
console.log("entered mouse over");
setHover(true);
}}
onMouseOut={() => {
console.log("entered onMouseOut");
setHover(false);
}}
className={styles.heightSet}
src={!hover ? mainLogo.publicURL : gifLogo.publicURL}
alt="Baozi Technology"
/>
</Link>
</div>
<div className={styles.toggleMenuContainer}>
<Drawer />
</div>
<div className={styles.navContainer}>
<nav className={styles.siteNav}>
<div className={styles.siteNavItemFirst}>
<Link activeClassName={styles.linkActive} to="/">
Home
</Link>
</div>
<div className={styles.siteNavItemMiddle}>
<Link
activeClassName={styles.linkActive}
to="/ranch-under-the-hood"
>
Ranch doc
</Link>
</div>
<div className={styles.siteNavItemMiddle}>
<Link activeClassName={styles.linkActive} to="/about">
About
</Link>
</div>
<div className={styles.siteNavItemLast}>
<a
title="[email protected]"
href="mailto:[email protected]"
>
Message me
</a>
</div>
</nav>
</div>
</div>
</header>
);
};
export default HeaderV1;
Breaking down the code above.
useState
hook call used.mainLogo
will contain the data for the original logo and gifLogo
the animated gif. img
element it will display the image conditionally based on the state( if you hovering or not).With this small change you're streamlining the code, removing the need for loop and avoiding managing multiple states.
2- Alternative 2, without any GraphQL:
I saw that you added the content inside the static folder and with that in mind i created another component without any GraphQL that will achieve the same end result with only one useState
hook making the component even leaner and simpler.
import React, { useState } from "react";
import { Link } from "gatsby";
import styles from "./Header.module.scss";
import Drawer from "../Drawer/Drawer";
const HeaderV2 = () => {
const [hover, setHover] = useState(false);
return (
<header className={styles.header}>
<div className={styles.headerContainer}>
<div title="Jump to home page" className={styles.logoContainer}>
<Link to="/">
<img
onMouseOver={() => {
console.log("entered mouse over");
setHover(true);
}}
onMouseOut={() => {
console.log("entered onMouseOut");
setHover(false);
}}
className={styles.heightSet}
src={!hover ? '/logos/baozi-technology-full-logo.jpg':'/logos/baozi-technology-full-logo.gif'}
alt="Baozi Technology"
/>
</Link>
</div>
<div className={styles.toggleMenuContainer}>
<Drawer />
</div>
<div className={styles.navContainer}>
<nav className={styles.siteNav}>
<div className={styles.siteNavItemFirst}>
<Link activeClassName={styles.linkActive} to="/">
Home
</Link>
</div>
<div className={styles.siteNavItemMiddle}>
<Link
activeClassName={styles.linkActive}
to="/ranch-under-the-hood"
>
Ranch doc
</Link>
</div>
<div className={styles.siteNavItemMiddle}>
<Link activeClassName={styles.linkActive} to="/about">
About
</Link>
</div>
<div className={styles.siteNavItemLast}>
<a
title="[email protected]"
href="mailto:[email protected]"
>
Message me
</a>
</div>
</nav>
</div>
</div>
</header>
);
};
export default HeaderV2;
What is happening here is the following, as you're using the static
folder, Gatsby will pick on it and grabs the files and places them inside the public
folder and with that you can grab them directly without the need of any graphql query. You can read up more about it in here and here.
I'll leave it up to you on how you wish to proceed. And it was no inconvenience whatsoever.
@jonniebigodes
Thanks a lot for your help. I agree that your solutions are much cleaner.
@NicoGim no need to thank, glad that i was able to offer you some insights.
@jonniebigodes
I actually have another small issue on my site. If you are happy to help, then I am happy too!
https://github.com/baozi-technology/baozi-web/blob/master/src/components/BioContent/BioContent.jsx
As you can see clicking on "eating baozi" in https://baozi.technology triggers a .gif of myself eating one. Cool.
I preload the two avatars in order to make the change as smooth as possible for first time users of the site.
However, for some reason, when clicking on "eating baozi" for the first time, it does take a little while for the avatar to change.
I wonder whether it is because of my use of setTimeout
...
Do you have a clue?
With kind regards,
Nicolas
N.B: have an awesome new year eve!
@NicoGim i took a look at the component and i used the same approach in this component that was used in my earlier response. Removing the need of almost all of the "moving parts" you have.
1- Starting with a simplified way with useStaticQuery
, i created a new component with the following content:
import React, { useState } from "react";
import { useStaticQuery, graphql, Link } from "gatsby";
import styles from "./BioContent.module.scss";
import PropTypes from "prop-types";
// the {isLink} is used to destructure the prop inside, that is injected in the parent component
const BioContentV1 = ({ isLink }) => {
const [eating,setIsEating]= useState(false)
const bioResult = useStaticQuery(graphql`
{
mainBio: file(relativePath: { eq: "avatars/nico_catch_yuting.jpg" }) {
publicURL
}
gifBio: file(
relativePath: { eq: "avatars/me-eating-baozi-square-transparent.gif" }
) {
publicURL
}
}
`);
// destructures the query result
const { mainBio, gifBio } = bioResult;
// click handler for the button triggers the
const handleBaoziClick = () => {
setIsEating(true);
setTimeout(() => {
setIsEating(false);
}, 2500);
};
return (
<div className={styles.bioContentContainer}>
<div className={styles.avatarContainer}>
<img
className={styles.avatar}
src={!eating?mainBio.publicURL:gifBio.publicURL}
alt="Oops, the profile pic did not load! Try refreshing the page."
/>
</div>
<div className={styles.bioContainer}>
{isLink ? (
<p className={styles.bioContent}>
<span>
Hi! I’m Nicolas – freelance fullstack software engineer based in
France. My main area of expertise is concurrent backend services
dealing with various non-standard protocols. My passions are
solving complex problems using technology and{" "}
<button type="button" className={styles.baozi} onClick={handleBaoziClick}>
eating baozi
</button>
.
</span>
{" "}
<Link className={styles.arrow} to="/about">
→
</Link>
</p>
) : (
<p className={styles.bioContent}>
{" "}
<span>
Hi! I’m Nicolas – freelance fullstack software engineer based in
France. My main area of expertise is concurrent backend services
dealing with various non-standard protocols. My passions are
solving complex problems using technology and{" "}
<button type="button" className={styles.baozi} onClick={handleBaoziClick}>
eating baozi
</button>
.
</span>
</p>
)}
</div>
</div>
);
};
export default BioContentV1;
BioContentV1.propTypes = {
isLink: PropTypes.bool
};
Same logic was applied, removed the excess useEffect
and aliased the GraphQL to retrieve exactly what i need, without the need to complicate things and unnecessary loops. The click handler handleBaoziClick
will set the state, to eating=true
wait for two and a half seconds, that was based on the code you had and a rough estimate of the duration of the gif. Then revert back. This strategy was used as it's a bit tricky to handle gifs with React without some extra ammout of work, that would complicate the component's logic. Removed the vars
assignement and moved to conditional rendering of the component based on the isLink
prop, making the component more simple and leaner.
import React, { useState } from "react";
import { Link } from "gatsby";
import styles from "./BioContent.module.scss";
import PropTypes from "prop-types";
// the {isLink} is used to destructure the prop inside, that is injected in the parent component
const BioContentV2 = ({ isLink }) => {
const [eating,setIsEating]= useState(false)
// click handler for the button triggers the
const handleBaoziClick = () => {
setIsEating(true);
setTimeout(() => {
setIsEating(false);
}, 2500);
};
return (
<div className={styles.bioContentContainer}>
<div className={styles.avatarContainer}>
<img
className={styles.avatar}
src={!eating?'avatars/nico_catch_yuting.jpg':'avatars/me-eating-baozi-square-transparent.gif'}
alt="Oops, the profile pic did not load! Try refreshing the page."
/>
</div>
<div className={styles.bioContainer}>
{isLink ? (
<p className={styles.bioContent}>
<span>
Hi! I’m Nicolas – freelance fullstack software engineer based in
France. My main area of expertise is concurrent backend services
dealing with various non-standard protocols. My passions are
solving complex problems using technology and{" "}
<button type="button" className={styles.baozi} onClick={handleBaoziClick}>
eating baozi
</button>
.
</span>
{" "}
<Link className={styles.arrow} to="/about">
→
</Link>
</p>
) : (
<p className={styles.bioContent}>
{" "}
<span>
Hi! I’m Nicolas – freelance fullstack software engineer based in
France. My main area of expertise is concurrent backend services
dealing with various non-standard protocols. My passions are
solving complex problems using technology and{" "}
<button type="button" className={styles.baozi} onClick={handleBaoziClick}>
eating baozi
</button>
.
</span>
</p>
)}
</div>
</div>
);
};
export default BioContentV2;
BioContentV2.propTypes = {
isLink: PropTypes.bool
};
As before as you're already using the static
folder there was no need for the GraphQL query, you can just grab the assets directly and display them, making the component even more functional.
Once again i'll leave it up to you on how you wish to proceed.
And an awesome new year eve to you aswell!
Most helpful comment
The correct syntax is:
You also need to use the
class
syntax, wrap your JSX in arender
function, and getchildren
anddata
fromthis.props
:If you want to know more about the how and why, I'd encourage you to look into the React tutorial. Of course, we are always happy to help as well!