I am unable to make my local images render in the development server of my gatsby website. I am using gatsby and tailwindcss. I am trying to combine a filterable photo gallery and a light box. My problem is that the file paths do not work unless I use the url through graphql. I think this also affecting my light box because if you click on an image it will not display it and also show image not found for the light box.
I have tried to configure the gatsby-source-filesystem file path to access my images folder in my src. I also matched the array source path to the /src/images/mbaglass1.jpeg. This rendered the alt tag.
I have tried to import the file path through the allFile in grapql. This is my first time using it. I was unable to render the images on the page in the manner I require.
I did import the url from graphql which is successfully rendering the images on the page, but I am unable to make it function with my light box.
I have created a functioning light box for a different website and I have been able to make this filter function in this website, but I could use some guidance towards successfully combining the two.
Here is the component:
import ImageLightbox from "react-image-lightbox"
import React from "react"
const images = [
{
id: 1,
src: "/src/images/mbaglass1.jpeg",
category: "Frameless Showers",
alt: "shower",
},
{
id: 3,
src: "/static/ddb59dd079ddf9b6b0caebc1a9f05078/mbaglass3.jpeg",
category: "Frameless Showers",
},
{
id: 2,
src: "/static/bbb4f3909b9421b16d474e1fa01805c4/mbaglass2.jpeg",
category: "Mirrors",
},
]
const categories = [
"All",
"Frameless Showers",
"Sliding Showers",
"Mirrors",
"Miscellaneous",
]
class GalleryDisplay extends React.Component {
constructor(props) {
super(props)
this.state = {
lightboxIsOpen: false,
currentImage: 0,
currentCategory: categories[0],
}
this.openLightbox = this.openLightbox.bind(this)
this.closeLightbox = this.closeLightbox.bind(this)
this.moveNext = this.moveNext.bind(this)
this.movePrev = this.movePrev.bind(this)
this.handleFilterChange = this.handleFilterChange.bind(this)
}
openLightbox(e, { index }) {
this.setState({
currentImage: index,
lightboxIsOpen: true,
})
}
closeLightbox() {
this.setState({
currentImage: 0,
lightboxIsOpen: false,
})
}
moveNext() {
this.setState({
currentImage: (this.state.currentImage + 1) % images.length,
})
}
movePrev() {
this.setState({
currentImage:
(this.state.currentImage - 1 + images.length) % images.length,
})
}
handleFilterChange(event) {
this.setState({
currentCategory: event.target.value,
})
}
render() {
<div>
<ImageLightbox
isOpen={this.state.lightboxIsOpen}
mainSrc={images[this.state.currentImage]}
nextSrc={images[(this.state.currentImage + 1) % images.length]}
prevSrc={
images[(this.state.currentImage - 1 + images.length) % images.length]
}
onCloseRequest={this.closeLightbox}
onMovePrevRequest={this.movePrev}
onMoveNextRequest={this.moveNext}
/>
</div>
return (
<>
<div className="flex flex-wrap justify-center py-4">
{categories.map(category => (
<button className="px-4 py-2 m-2 rounded-lg text-center text-white bg-blue-500 hover:bg-blue-600" onClick={() => this.handleFilterChange({target: {value: category}})}>{category}</button>
))}
</div>
<div className="grid p-8 lg:p-16 gap-x-4 gap-y-8 sm:grid-cols-0 sm:gap-x-4 md:grid-cols-2 md:gap-x-8 lg:grid-cols-3 xl:gap-x-12" >
{images
.filter(
image =>
image.category === this.state.currentCategory ||
this.state.currentCategory === "All"
)
.map((image, index) => (
<a key={index} onClick={e => this.openLightbox(e, { index })}>
<img key={image.src} className="rounded-lg" src={image.src} alt={image.alt} />
</a>
))}
</div>
</>
)
}
}
export default GalleryDisplay;
Here is the gatsby-config file:
/**
* Configure your Gatsby site with this file.
*
* See: https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/
*/
/**
* #type {import('gatsby').GatsbyConfig}
*/
module.exports = {
siteMetadata: {
siteMetadata: {
title: `Gatsby`,
siteUrl: `https://www.gatsbyjs.com`,
description: `Blazing fast modern site generator for React`,
},
},
plugins: [
`gatsby-plugin-image`,
'gatsby-plugin-postcss',
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
// This will impact how browsers show your PWA/website
// https://css-tricks.com/meta-theme-color-and-trickery/
// theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
],
}
I was able to resolve this by first importing the image at the top of the component e.g. import mbaglass2 from "../mbaglass2.jpeg", then using its import name mbaglass2 in the array source. It seems it was a simple fix that way overlooked.
Related
I am trying to create FAQ page in my react project using below package:
https://www.npmjs.com/package/react-faq-component
I am able to show FAQ with 1 category.
I want to show questions/answers with different categories:
Code:
import React, { useState } from 'react';
import FaqData from 'react-faq-component';
function Faq() {
const [rows, setRowsOption] = useState(null);
const data = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
return (
<div>
<h2 className="section-title">My FAQ's</h2>
<div className="faq-style-wrapper">
<FaqData data={data} getRowOptions={setRowsOption} />
</div>
</div>
);
}
If any other demo/library can give me desired output, please suggest those as well.
As suggested by #Arkellys I have used one component per category & its worked for me. Thanks again #Arkellys.
I am adding answer below for more details:
import React, { useState } from 'react';
import FaqData from 'react-faq-component';
function Faq() {
const [rows, setRowsOption] = useState(null);
const data1 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
const data2 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
const data3 = {
title: 'FAQ (how it works)',
rows: [
{
title: 'How do I change my password?',
content: `Answer here.`,
},
{
title: 'How do I sign up?',
content:'Answer here.',
},
],
};
return (
<div>
<h2 className="section-title">My FAQ's</h2>
<div className="faq-style-wrapper">
<FaqData data={data1} getRowOptions={setRowsOption} />
<FaqData data={data2} getRowOptions={setRowsOption} />
<FaqData data={data3} getRowOptions={setRowsOption} />
</div>
</div>
);
}
Background info:
I'm using react and material-ui.
To keep the code clean, I populate menu items from a const array, like so:
const menuItems = [
{ label: "Home", path: "/home" },
{ label: "Accounts", path: "/accounts" },
{ label: "Organizations", path: "/organizations" },
];
Each item in the array is an object containing a label and a redirect path. I map over the items when rendering. Very basic.
Problem:
I would like to include a material-ui icon component in the menuItems array so the icon can be rendered next to the label. But I can't find a way to reference the icons by a name string
https://material-ui.com/components/material-icons/
Possible solutions:
put the icon component into a string:
{ label: "Accounts", path: "/accounts" }, icon: "<AccountBox/>"} but then I somehow need to evaluate the string into jsx. I don't know how.
Make a react functional component which renders a different icon depending on a prop, for example: <IconSwitch icon = {"accountIcon"} /> and hard-code different icons inside the RFC. Not pretty, but should work.
Punt and use different icons such as svg icons or font icons that can referenced by a name string.
Any suggestions on how to do this?
Thanks
Icon Font
You can use the Icon component. https://material-ui.com/components/icons/#icon-font-icons
To use an icon simply wrap the icon name (font ligature) with the Icon component, for example:
import Icon from '#material-ui/core/Icon';
<Icon>star</Icon>
https://codesandbox.io/s/material-demo-forked-sj66h?file=/demo.tsx
Assuming you set up your menu items with the appropriate icon ligatures:
const menuItems = [
{ label: "Home", path: "/home", icon: "home" },
{ label: "Accounts", path: "/accounts", icon: "account_circle" },
{ label: "Organizations", path: "/organizations", icon: "settings" }
];
Then you can map over them:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} <Icon>{icon}</Icon>
</span>
);
})}
SVG Icons
If you want to use SVG icons instead of basic icons, I'd recommend pulling only the SVG icons you plan to use in order to allow the icons you aren't using to be tree-shaken from the resulting bundle. The ability to tree shake is a good reason to use SVG icons over font icons.
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
If you want to allow user input of all icons or aren't aware ahead of time which icons will be displayed, you can import everything from #material-ui/icons as in Jonathan's answer.
If you aren't putting the list of icons into something that needs to be able to be stringified (i.e. Redux/sent through an API call) then you can just directly put the icons into the array and render them:
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: <Home /> },
{ label: "Accounts", path: "/accounts", icon: <AccountCircle /> },
{ label: "Organizations", path: "/organizations", icon: <Settings /> }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} {icon}
</span>
);
})}
If you are going to put the Icons somewhere that needs to be stringified, the above won't work, so I'd recommend putting the icons you want to use into an object to map them. That way you have a string to icon map.
Example: https://codesandbox.io/s/material-icons-svg-udcv3?file=/demo.tsx
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
const icons = {
Home,
AccountCircle,
Settings
};
In the case of the example above (i.e. rendering the icons from an array)
interface MenuItem {
label: string;
path: string;
icon: keyof typeof icons;
}
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: "Home" },
{ label: "Accounts", path: "/accounts", icon: "AccountCircle" },
{ label: "Organizations", path: "/organizations", icon: "Settings" }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
const Icon = icons[icon];
return (
<span key={label}>
{label} <Icon />
</span>
);
})}
You can import all from #material-ui/icons and than create an Icon component dynamically:
import React from 'react'
import * as icons from '#material-ui/icons'
interface MenuItem {
label: string,
icon: keyof typeof icons,
path: string
}
export function Menu() {
const menuItems: MenuItem[] = [
{ label: 'Home', path: './home', icon: 'Home' },
{ label: 'Accounts', path: './accounts', icon: 'AccountCircle' },
{ label: 'Organizations', path: './organizations', icon: 'Settings' }
]
return (
<>
{menuItems.map(menuItem => {
const Icon = icons[menuItem.icon]
return (
<span key={menuItem.path}>
{menuItem.label} <Icon />
</span>
)
})}
</>
)
}
// I have better way to avoid all of this other hustle .
// 1: Make Every icon in Array which is in Jsx from to simple name.
// Ex:
[
{ Name: "New", Icon: <HomeIcon /> },
{ Name: "JS Mastery", Icon: <CodeIcon /> },
{ Name: "Coding", Icon: <CodeIcon /> },
{ Name: "ReactJS", Icon: <CodeIcon /> },
{ Name: "NextJS", Icon: <CodeIcon /> },
]
to
[
({ Name: "New", Icon: HomeIcon },
{ Name: "JS Mastery", Icon: CodeIcon },
{ Name: "Coding", Icon: CodeIcon },
{ Name: "ReactJS", Icon: CodeIcon })
];
// 2: Remember Using Object keys name as capital ,here:- "Name , Icon" not "name , icon".
// 3: Now Simply use : -
{
categories.map(({ Name, Icon }) => (
<button key={Name}>
<span>{Name}</span>
<span> {<Icon/>} </span>
</button>
));
}
//use icon in this cleaver way
I have a React Bootstrap Table2 table functional component which gets data from a single API (at the moment) and presents the data in a table.
As part of the column definition of the table I process certain values and make them links. When a user clicks the link they will go to a page that renders the same table but with a subset of the data.
I am trying to add some logic to the component so that I can associate the various link paths with different API endpoints (e.g. if path is foo, api end point is bar, if path is foo2, api endpoint is bar2 - which then get passed to axios).
My thought was to try to use props.match.path and when there are multiple parts in the path (/section1/sub-section/sub-sub-section) break these apart and map to API endpoints.
However I cannot access props in my functional component. Is my approach the best way to do this (and if so how can I best access the data I need), or is there a better way to do this?
import BootstrapTable from "react-bootstrap-table-next";
import paginationFactory from "react-bootstrap-table2-paginator";
import React, { useState, useEffect } from "react";
import axios from "axios";
const columns = [
{
dataField: "_id",
hidden: true,
},
{
dataField: "card_id",
text: "Card Id",
sort: true,
},
{
dataField: "team",
text: "Team",
formatter: (rowContent, row) => {
console.log(row);
return (
<>
{/* {rowContent} */}
<a
target="_blank"
rel="noopener noreferrer"
// href={`${links[row.id]}`}
href={`http://www.bbc.co.uk/${rowContent}`}
// href={`http://www.bbc.co.uk/${row.card_id}`}
style={{
marginLeft: "12px",
}}
>
{rowContent}
</a>
</>
);
},
},
{
dataField: "brand",
text: "Brand",
sort: true,
},
{
dataField: "career_stage",
text: "Career Stage",
sort: true,
},
{
dataField: "forTrade",
text: "For Trade",
sort: true,
},
{
dataField: "player",
text: "Player",
formatter: (rowContent, row) => {
console.log(row);
return (
<>
{/* {rowContent} */}
<a
target="_blank"
rel="noopener noreferrer"
// href={`${links[row.id]}`}
href={`http://www.bbc.co.uk/${rowContent}`}
// href={`http://www.bbc.co.uk/${row.card_id}`}
style={{
marginLeft: "12px",
}}
>
{rowContent}
</a>
</>
);
},
},
{
dataField: "series",
text: "series",
},
/* {
dataField: "price",
text: "Price",
formatter: (cell, row) => {
return <p>${cell}</p>;
},
sort: true,
sortFunc: (a, b, order, dataField, rowA, rowB) => {
const numA = parseFloat(a);
const numB = parseFloat(b);
if (order === "asc") {
return numB - numA;
}
return numA - numB; // desc
},
}, */
];
const BasicTable = () => {
const [data, setData] = useState([]);
console.log(data);
useEffect(() => {
axios
.get(
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/cards-fvyrn/service/Cards/incoming_webhook/getAllCards"
)
.then((response) => {
setData(response.data);
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}, []);
return (
<BootstrapTable
keyField="id"
data={data}
columns={columns}
striped
hover
condensed
pagination={paginationFactory()}
/>
);
};
export default BasicTable;
Edit - OK, sorry for the stupid question. RTFM. I've posted an answer in case it's useful for other newbies who are following tutorials other than the one n the React site.
Sorry for the dumb question. If it's useful to anyone yu just need to pass in props as an argument const SingleCard = (props) => {....} then use in the component e.g. props.match.path
I'm using Gatsby/Netlify CMS stack and have been trying to display markdown file contents on the main page. For example, I have a directory in src/pages/experience that displays all of the experience markdown files.
So using graphql, I have a query that actually works:
{
allMarkdownRemark(
limit: 3,
sort: { order: DESC, fields: [frontmatter___date] },
filter: { fileAbsolutePath: { regex: "/(experience)/" } }
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
However, when running it on my component page it displays ×
TypeError: Cannot read property 'allMarkdownRemark' of undefined
However after entering this before return:
if (!data) { return null };
The error goes away but the entire section disappears. Here it is below:
const Experience = ({data}) => {
return (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
{data.allMarkdownRemark.edges.map(({node}) => (
<div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
<div className="text-block"><strong>{node.frontmatter.title}</strong></div>
<div className="text-block-4">{node.frontmatter.company_role}</div>
<div className="text-block-4">{node.frontmatter.location}</div>
<div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
<p className="paragraph">{node.frontmatter.excerpt}</p>
<div className="skill-div">{node.frontmatter.tags}</div>
</div>
))}
</div>
</div>
</div>
)}
export default Experience
In gatsby-config-js, I've added a gatsby-source-filesystem resolve separate from /src/posts to /src/pages where the experience directory is src/pages/experience.
Update: 2/7/2019
Here is the gatsby-config-js file:
module.exports = {
siteMetadata: {
title: `Howard Tibbs Portfolio`,
description: `This is a barebones template for my portfolio site`,
author: `Howard Tibbs III`,
createdAt: 2019
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
{
resolve: 'gatsby-remark-images',
},
],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `posts`,
path: `${__dirname}/src/posts`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `pages`,
path: `${__dirname}/src/pages`,
},
},
`gatsby-plugin-netlify-cms`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
`gatsby-transformer-sharp`
],
}
What I feel is that somewhere in gatsby-node-js, I did not create a instance to do something with that type query.
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const PostTemplate = path.resolve('./src/templates/post-template.js')
const BlogTemplate = path.resolve('./src/templates/blog-template.js')
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode, basePath: 'posts' })
createNodeField({
node,
name: 'slug',
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
{
allMarkdownRemark (limit: 1000) {
edges {
node {
fields {
slug
}
}
}
}
}
`)
const posts = result.data.allMarkdownRemark.edges
posts.forEach(({ node: post }) => {
createPage({
path: `posts${post.fields.slug}`,
component: PostTemplate,
context: {
slug: post.fields.slug,
},
})
})
const postsPerPage = 2
const totalPages = Math.ceil(posts.length / postsPerPage)
Array.from({ length: totalPages }).forEach((_, index) => {
const currentPage = index + 1
const isFirstPage = index === 0
const isLastPage = currentPage === totalPages
createPage({
path: isFirstPage ? '/blog' : `/blog/${currentPage}`,
component: BlogTemplate,
context: {
limit: postsPerPage,
skip: index * postsPerPage,
isFirstPage,
isLastPage,
currentPage,
totalPages,
},
})
})
}
Wanted to know if anyone was able to get something similar to work? Greatly appreciate your help.
Update: 2/6/2019
So made some changes to my code from pageQuery to StaticQuery and unfortunately it still doesn't work but I believe it is going the right direction:
export default() => (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
<StaticQuery
query={graphql`
query ExperienceQuery {
allMarkdownRemark(
limit: 2,
sort: { order: DESC, fields: [frontmatter___date]},
filter: {fileAbsolutePath: {regex: "/(experience)/"}}
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`}
render={data => (
<div className="column-2 w-col w-col-4 w-col-stack" key={data.allMarkdownRemark.id}>
<div className="text-block"><strong>{data.allMarkdownRemark.frontmatter.title}</strong></div>
<div className="text-block-4">{data.allMarkdownRemark.frontmatter.company_role}</div>
<div className="text-block-4">{data.allMarkdownRemark.frontmatter.location}</div>
<div className="text-block-3">{data.allMarkdownRemark.frontmatter.work_from} – {data.allMarkdownRemark.frontmatter.work_to}</div>
<p className="paragraph">{data.allMarkdownRemark.frontmatter.excerpt}</p>
<div className="skill-div">{data.allMarkdownRemark.frontmatter.tags}</div>
</div>
)}
/>
</div>
</div>
</div>
);
I get this error TypeError: Cannot read property 'title' of undefined
So what I'm trying to accomplish is this instance across this section. Of course this is a placeholder but I'm looking to replace that placeholder with the contents of each markdown.
Experience snip
Update: 2/7/2019
So no changes today but wanted to post a few fields to get a better perspective of what I'm trying to do. This is the config.yml file from NetlifyCMS where it is displaying the collections. This is what I'm accomplishing (Note: the test repo is just to see the actual CMS, I will look to change):
backend:
name: test-repo
branch: master
media_folder: static/images
public_folder: /images
display_url: https://gatsby-netlify-cms-example.netlify.com/
# This line should *not* be indented
publish_mode: editorial_workflow
collections:
- name: "experience"
label: "Experience"
folder: "experience"
create: true
fields:
- { name: "title", label: "Company Title", widget: "string" }
- { name: "company_role", label: "Position Title", widget: "string" }
- { name: "location", label: "Location", widget: "string" }
- { name: "work_from", label: "From", widget: "date", format: "MMM YYYY" }
- { name: "work_to", label: "To", default: "Present", widget: "date", format: "MMM YYYY" }
- { name: "description", label: "Description", widget: "text" }
- { name: "tags", label: "Skills Tags", widget: "select", multiple: "true",
options: ["ReactJS", "NodeJS", "HTML", "CSS", "Sass", "PHP", "Typescript", "Joomla", "CMS Made Simple"] }
- name: "blog"
label: "Blog"
folder: "blog"
create: true
slug: "{{year}}-{{month}}-{{day}}_{{slug}}"
fields:
- { name: path, label: Path }
- { label: "Image", name: "image", widget: "image" }
- { name: title, label: Title }
- { label: "Publish Date", name: "date", widget: "datetime" }
- {label: "Category", name: "category", widget: "string"}
- { name: "body", label: "body", widget: markdown }
- { name: tags, label: Tags, widget: list }
- name: "projects"
label: "Projects"
folder: "projects"
create: true
fields:
- { name: date, label: Date, widget: date }
- {label: "Category", name: "category", widget: "string"}
- { name: title, label: Title }
- { label: "Image", name: "image", widget: "image" }
- { label: "Description", name: "description", widget: "text" }
- { name: body, label: "Details", widget: markdown }
- { name: tags, label: Tags, widget: list}
- name: "about"
label: "About"
folder: "src/pages/about"
create: false
slug: "{{slug}}"
fields:
- {
label: "Content Type",
name: "contentType",
widget: "hidden",
default: "about",
}
- { label: "Path", name: "path", widget: "hidden", default: "/about" }
- { label: "Title", name: "title", widget: "string" }
- { label: "Body", name: "body", widget: "markdown" }
And for an example of a markdown page, this would be the format to look for in the Experience section, because as you see in the picture it displays across the container:
---
title: Test Company
company_role: Test Role
location: Anytown, USA
work_from: January, 2020
work_to: January, 2020
tags: Test, Customer Service
---
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
Update: 2/8/2019
I do have some updates with the code provided below but before I get into it, here are a few images of what I'm looking to accomplish. These are placeholders that I'm looking to replace for real data. This is for each section:
Full Experience snip
Projects snip
Blog snip
I've ran the code that was provided by #staypuftman in the answer below and came up with this error:
Your site's "gatsby-node.js" created a page with a component that
doesn't exist.
I've added the code in addition to what was already there and it processed that error. This is what I originally thought would happen and the reason I wanted to use StaticQuery independently. This was actually the main issue I had with the documentation and the starter repos, no one really has created multiple variables in node.js.
I also tried the revision from #DerekNguyen which looked like this:
import React from "react"
import { Link, graphql, StaticQuery } from "gatsby"
export default(data) => (
<div id="experience" className="section accent">
<div className="w-container">
<div className="section-title-group">
<Link to="#experience"><h2 className="section-heading centered">Experience</h2></Link>
</div>
<div className="columns w-row">
<StaticQuery
query={graphql`
query ExperienceQuery {
allMarkdownRemark(
limit: 2,
sort: { order: DESC, fields: [frontmatter___date]},
filter: {fileAbsolutePath: {regex: "/(experience)/"}}
) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`}
render={data.allMarkdownRemark.edges.map(({ node }) => (
<div className="column-2 w-col w-col-4 w-col-stack" key={node.id}>
<div className="text-block"><strong>{node.frontmatter.title}</strong></div>
<div className="text-block-4">{node.frontmatter.company_role}</div>
<div className="text-block-4">{node.frontmatter.location}</div>
<div className="text-block-3">{node.frontmatter.work_from} – {node.frontmatter.work_to}</div>
<p className="paragraph">{node.frontmatter.excerpt}</p>
<div className="skill-div">{node.frontmatter.tags}</div>
</div>
))}
/>
</div>
</div>
</div>
);
However that also came with an error as well:
TypeError: Cannot read property 'edges' of undefined
Still working on it, but I think it is getting closer to the solution. Keep in mind I also would have to create it for the other variables.
Update: 2/10/2019
For those who want to see how I constructed the site using gatsby-starter, here it is below:
My portfolio
gastby-node.js is used when you have a bunch of pages that need to be at /pages/{variable-here}/. Gatsby uses gatsby-node.js to run a GraphQL query against your data source (Netlify CMS in this case) and grabs all the content needed based on your particular GraphQL query.
It then dynamically builds out X number of pages using a component in your project. How many pages it builds depends on what it finds at the remote data source. How they look depends on the component you specify. Read more about this in the Gatsby tutorial.
Staticquery is used to get one-time data into components, not to generate pages from a data source. It is highly useful but not what I think you're trying to do. Read more about it on the Gatsby site.
Based on all of this and what you've provided above, I think your gatsby-node.js should look this:
// Give Node access to path
const path = require('path')
// Leverages node's createPages capabilities
exports.createPages = async ({ graphql, actions }) => {
// Destructures createPage from redux actions, you'll use this in a minute
const { createPage } = actions
// Make your query
const allExperiencePages = await graphql(`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
id
frontmatter {
title
company_role
location
work_from
work_to
tags
}
excerpt
}
}
}
}
`)
// gatsby structures allExperiencePages into an object you can loop through
// The documentation isn't great but the 'data' property contains what you're looking for
// Run a forEach with a callback parameter that contains each page's data
allExperiencePages.data.allMarkdownRemark.edges.forEach( page => {
// Make individual pages with createPage variable you made earlier
// The 'path' needs to match where you want the pages to be when your site builds
// 'conponent' is what Gatsby will use to build the page
// 'context' is the data that the component will receive when you `gatsby build`
createPage({
path: `/pages/${page.node.title}/`,
component: path.resolve('src/components/Experience'),
context: {
id: page.node.id,
title: page.node.frontmatter.title,
company_role: page.node.frontmatter.company_role,
location: page.node.frontmatter.location,
work_from: page.node.frontmatter.work_from,
work_to: page.node.frontmatter.work_to,
tags: page.node.frontmatter.tags,
excerpt: page.node.excerpt
}
})
})
}
This alone may not be enough to generate a page! It all depends on what's going on with the component you specify in the createPage component part of the gatsby-node.js file.
I'm trying to pull some data from Google Analytics reporting API and display the data inside the apex charts library. I managed to successfully do this. However, I now want filtering options to where if the user selects a certain date within the date range picker react wrapper, the apex charts data gets updated from the API.
I'm struggling figuring out on how when my data gets updated, to update the state with the new state within my life cycle method? I think I'm doing something minor I just don't know what it is. I looked up the documentation on the life cycle method and it says to make sure to wrap it inside a condition which I did. However, when the else condition is met, it causes an infinite render.
Here is my code: (the bug i'm stuck on is the componentWillUpdate lifecycle method) everything else works fine.
import React from "react";
import Header from "../common/Header";
import Footer from "../common/Footer";
import moment from "moment";
import $ from "jquery";
import ApexCharts from "apexcharts";
import Chart from "react-apexcharts";
import DateRangePicker from "react-bootstrap-daterangepicker";
const VIEW_ID = "";
class Charts extends React.Component {
constructor(props) {
super(props);
this.printResults = this.printResults.bind(this);
this.pageViews = this.pageViews.bind(this);
this.handleError = this.handleError.bind(this);
this.state = {
loading: true,
filterstartDate: "",
filterendDate: "",
// Start Series Bar State
ChartOne: {
chart: {
id: "ChartOne"
},
colors: ["#e31d1a"],
xaxis: {
categories: [],
labels: {
style: {
colors: []
}
},
title: {
text: "Locations"
}
},
yaxis: {
labels: {
style: {
colors: []
}
},
title: {
text: "Count"
}
}
},
ChartOneSeries: [],
}
pageViews = async () => {
window.gapi.client
.request({
path: "/v4/reports:batchGet",
root: "https://analyticsreporting.googleapis.com",
method: "POST",
body: {
reportRequests: [
{
viewId: VIEW_ID,
dateRanges: [
{
startDate: "7daysAgo",
endDate: "today"
}
],
metrics: [
{
expression: "ga:pageviews"
}
],
dimensions: [
{
name: "ga:country"
}
],
orderBys: [{ fieldName: "ga:pageviews", sortOrder: "DESCENDING" }]
}
]
}
})
.then(this.printResults, this.handleError);
};
componentDidMount() {
$.getScript("https://apis.google.com/js/client:platform.js").done(() => {
window.gapi.signin2.render("my-signin2", {
scope: "profile email",
width: 240,
height: 50,
longtitle: true,
theme: "dark",
onsuccess: this.pageViews,
onfailure: this.handleError
});
});
}
//log the data
printResults(response) {
let pageviewLocation = [];
let pageviewCount = [];
let pageviewTotal = response.result.reports[0].data.totals[0].values[0];
let totalComma = pageviewTotal
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
response.result.reports[0].data.rows.map(value => {
//console.log(value.dimensions);
pageviewLocation.push(value.dimensions[0]);
pageviewCount.push(parseInt(value.metrics[0].values[0]));
});
//console.log(total);
this.setState({
loading: false,
ChartOne: {
title: {
text: totalComma,
align: "center",
style: {
fontSize: "20px"
}
},
subtitle: {
text: "Total Page Views",
align: "center",
style: {
fontSize: "14px",
cssClass: "apexcharts-yaxis-title"
}
},
plotOptions: {},
...this.state.ChartOne,
xaxis: {
width: 1,
...this.state.ChartOne.xaxis,
labels: {
show: false,
...this.state.ChartOne.xaxis.labels,
style: {
...this.state.ChartOne.xaxis.labels.style
}
},
categories: pageviewLocation
},
yaxis: {
min: 0,
...this.state.ChartOne.yaxis,
labels: {
//show: false,
...this.state.ChartOne.yaxis.labels,
style: {
...this.state.ChartOne.yaxis.labels.style
}
}
}
},
ChartOneSeries: [
{
name: "Total Page Views",
data: pageviewCount
}
]
});
}
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
// this fails immediately once the condition is met
test: "success!"
});
}
}
Datepicker = async (event, picker) => {
this.setState({
filterstartDate: moment(picker.startDate._d).format("YYYY-MM-DD"),
filterendDate: moment(picker.endDate._d).format("YYYY-MM-DD")
});
//console.log(this.state);
};
//or the error if there is one
handleError(reason) {
console.error(reason);
console.error(reason.result.error.message);
}
render() {
//console.log();
return (
<div className="containerfluid" id="fullWidth">
<Header />
<div className="container" id="chartContainer">
<h1>Site Analytics</h1>
<div className="row">
<div className="col-md-12">
<DateRangePicker
startDate={moment().format("MM-DD-YYYY")}
endDate={moment().format("MM-DD-YYYY")}
onApply={this.Datepicker}
>
<button className="btn btn-info">
<i className="fas fa-filter">
<span
style={{
fontFamily: "Roboto, san-serif",
fontWeight: "normal",
padding: "5px"
}}
>
Filter Date
</span>
</i>
</button>
</DateRangePicker>
</div>
</div>
<div className="row">
<div className="col-md-4">
{/* Chart One Line */}
{this.state.loading ? (
<React.Fragment>
<i className="fas fa-spinner fa-3x" id="loader" /> Please wait
...!
</React.Fragment>
) : (
<div className="chartContainer">
<Chart
options={this.state.ChartOne}
series={this.state.ChartOneSeries}
type="line"
width={400}
height={300}
/>
</div>
)}
</div>
</div>
<div id="my-signin2" />
</div>
<Footer />
</div>
);
}
}
export default Charts;
When you use setState you're triggering the lifecycle again. If you don't set your filterstartDate and filterendDate to "", you'll keep calling setState infinitely.
componentDidUpdate(prevProps, prevState) {
if (this.state.filterstartDate === "" && this.state.filterendDate === "") {
console.log("they are empty");
} else {
this.setState({
filterstartDate: "",
filterendDate: "",
test: "success!"
});
}
}