How to map nested array in gatsby with reactjs and graphql - arrays

I have a component, menu.js, that i import into a page to produce a list of articles, that can be filtered by category. This works perfectly.
Now i want to change the component so that i can filter the articles by tags. The problem is that the tags are a nested array in graphql, that i cant reach with the same map() function that maps the categories.
I have tried to do a nested map function but i cant get it to work, but i suspect that is the solution. My goal is to have the same functionality where i can filter the articles by tags, instead of by category. I hope thats possible. I am using gatsby, with a Strapi backend. Any hints in the right direction appreciated :-)
/src/pages/articles.js
import graphql from 'gatsby'
import React from 'react'
import Layout from 'components/layout'
import MenuBlog from 'components/menublog'
const BlogPage = ({ data }) => (
<Layout>
<MenuBlog items={data.menu} />
</Layout>
)
export default BlogPage
export const pageQuery = graphql`
query BlogQuery {
menu: allStrapiArticle {
edges {
node {
id
title
slug
tag {
title
id
}
category {
title
id
}
}
}
}
}
`
This is what i get back from the GraphQL query above, each article can of course have one or more tags, but only one category assigned
{
"data": {
"menu": {
"edges": [
{
"node": {
"title": "articleName 1",
"slug": "articleName-1",
"category": {
"title": "cat1"
},
"tag": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag3"
}
]
}
},
{
"node": {
"title": "articleName 2",
"slug": "articleName-2",
"category": {
"title": "cat2"
},
"tag": [
{
"title": "tag3"
}
]
}
}
]
}
}
}
And here is my component that displays the articles according to the chosen category
/src/components/menublog/index.js
import React, { Component } from 'react'
import { Link } from 'gatsby'
import Row from 'react-bootstrap/Row'
const getCategories = items => {
let tempItems = items.map(items => {
return items.node.category.title
})
let tempCategories = new Set(tempItems)
let categories = Array.from(tempCategories)
categories = ['all', ...categories]
return categories
}
export default class MenuBlog extends Component {
constructor(props) {
super(props)
this.state = {
items: props.items.edges,
articles: props.items.edges,
categories: getCategories(props.items.edges),
}
}
handleItems = category => {
let tempItems = [...this.state.items]
if (category === 'all') {
this.setState(() => {
return { articles: tempItems }
})
} else {
let items = tempItems.filter(
({ node }) => node.category.title === category
)
this.setState(() => {
return { articles: items }
})
}
}
render() {
if (this.state.items.length > 0) {
return (
<Row>
{/* items */}
<div className="col-md-8 blog-main bg-light">
<h1>Artikler</h1>
{this.state.articles.map(({ node }) => {
return (
<div key={node.id} className="blog-post mb-4">
<h2>
<Link to={`/artikler/${node.slug}`}>{node.title}</Link>
</h2>
{/* item text */}
</div>
)
})}
</div>
{/* categories */}
<div className="col-md-4 blog-sidebar">
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
<ol className="list-unstyled mb-0">
{this.state.categories.map((category, index) => {
return (
<li key={index}>
<button
type="button"
className="btn"
onClick={() => {
this.handleItems(category)
}}
>
{category}
</button>
</li>
)
})}
</ol>
</div>
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
</div>
</div>
</Row>
)
} else {
return <h1>no items</h1>
}
}
}

You should be able to use something similar to your category method:
items = tempItems.filter(({ node }) =>
node.tag.map(tag => tag.title).includes("tag2")
);
Since this isn't necessarily React / Gatsby specific, here is only the data and these methods:
const data = {
data: {
menu: {
edges: [{
node: {
title: "articleName 1",
slug: "articleName-1",
category: {
title: "cat1"
},
tag: [{
title: "tag1"
},
{
title: "tag2"
},
{
title: "tag3"
}
]
}
},
{
node: {
title: "articleName 2",
slug: "articleName-2",
category: {
title: "cat2"
},
tag: [{
title: "tag3"
}]
}
}
]
}
}
};
let items = data.data.menu.edges.filter(
({
node
}) => node.category.title === "cat2"
);
console.log(items);
items = data.data.menu.edges.filter(({
node
}) =>
node.tag.map(tag => tag.title).includes("tag2")
);
console.log(items);

Related

change text of a specific button when clicked in React

I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;

How to pass an array of objects as a prop in react?

I'm trying to pass an array of objects to a function on react, I'm getting an error of Uncaught TypeError: props.map is not a function although when I print the props, namely users, to the log before passing it on, it is an array
I have the following data:
{
"name": "My test name",
"users": [
{
"name": "user 1 name",
"data": [
{
"var1": [],
"var2": {
"sub1": "open",
"sub2": ""
},
},
{
"var1": [],
"var2": {
"sub1": "close",
"sub2": ""
},
},
]
}
]}
The data is received by a REST API call.
I'm trying to process it using the following react code:
interface SingleUser {
var1: string[];
var2: {
sub1: string;
sub2: string;
}
}
interface Users {
name: string;
users:SingleUser[];
}
render() {
const { name, users} = this.state;
return (
<div className='flow d-flex flex-row flex-wrap justify-content-start'>
<GenInfo name={name} />
<MyTabs {...users}/>
</div>
);}
function MyTabs(props: Users[]) {
const data = props
const tabs = props.map((item) => {
return {
tabId: item.name.replace(/ /g,"_"),
label: item.name,
content: <TestTable {...item.users} />
}
});
return <HorizontalTabs tabs={tabs} pills />
}
You can define props ò MyTabs as data like this
render() {
const { name, users} = this.state;
return (
<div className='flow d-flex flex-row flex-wrap justify-content-start'>
<GenInfo name={name} />
<MyTabs data={users}/>
</div>
);}
function MyTabs({data: Users[]}) {
const tabs = data.map((item) => {
return {
tabId: item.name.replace(/ /g,"_"),
label: item.name,
content: <TestTable {...item.users} />
}
});
return <HorizontalTabs tabs={tabs} pills />
}

How to map multiple yaml files to frontmatter in Gatsby

The problem:
I am having trouble mapping a second .yaml file to a second markdown frontmatter field in Gatsby. The project is for a magazine, with the first yaml file storing details about each author and the second file storing details about each issue.
The author.yaml file worked like it was supposed to, and I can query it across every template and page, but the issue.yaml file only works in two places: templates where I specifically passed its data as page context in gatsby-node, and (inexplicably) for markdown files with the frontmatter "featured" field === true. For mapped arrays in which "featured" is not present, or when it's false, every query that should pull data from the issue.yaml gives "TypeError: Cannot read property 'id' of null."
My suspicion is that it's because the issue field on my markdown files isn't the first field (which is author, and already mapped to the author.yaml file). As far as I can tell, I've implemented both yaml files exactly the same.
Things I've tried:
I've tried to query the allAuthorYaml and allIssueYaml nodes that I think are supposed to be automatically generated, but I couldn't get either of them to work. I also tried to create a schema customization in gatsby-node, but I couldn't figure out how to adapt the tutorial to define the nodes I needed globally (way over my head). I read somewhere that I could import the yaml data directly with import YAMLData from "../data/issue.yaml" and then create an array directly, but it also gave me null/undefined errors.
I am a hobbyist, and don't have a background in programming at all, which is probably a big part of the problem. Still, I'd be grateful for any help anyone might have. And if anyone happens to spot any other places my code needs improving, definitely let me know!
The starter I used was https://www.gatsbyjs.org/starters/JugglerX/gatsby-serif-theme/.
My repository is at https://github.com/ljpernic/HQ3.1/tree/HQhelp.
Thanks again!
My gatsby-config:
module.exports = {
siteMetadata: {
title: 'Haven Quarterly',
description: 'a magazine of science fiction and fantasy',
submit: {
phone: 'XXX XXX XXX',
email: 'havenquarterly#gmail.com',
},
menuLinks: [
{
name: 'Fiction',
link: '/fiction',
},
{
name: 'Non-fiction',
link: '/non-fiction',
},
{
name: 'Letters from the Future',
link: '/future',
},
{
name: 'Full Issues',
link: '/fullissues',
},
{
name: 'Contributors',
link: '/contributors',
},
{
name: 'About',
link: '/about',
},
{
name: 'Support',
link: '/support',
},
{
name: 'Submit',
link: '/submit',
},
],
},
plugins: [
'gatsby-plugin-sass',
'gatsby-transformer-json',
'gatsby-transformer-remark',
'gatsby-plugin-react-helmet',
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
`gatsby-plugin-catch-links`,
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/pages`,
name: 'pages',
},
},
/* {
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/posts`,
name: 'posts',
},
},*/
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/data`,
name: 'data',
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/images`,
name: 'images',
},
},
{
resolve: "gatsby-transformer-remark",
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1200,
quality: 95,
},
},
],
},
},
{
resolve: 'gatsby-plugin-google-analytics',
options: {
trackingId: guid ? guid : 'UA-XXX-1',
// Puts tracking script in the head instead of the body
head: false,
},
},
`gatsby-transformer-yaml`,
],
mapping: {
// 3. map author to author.yaml
"MarkdownRemark.frontmatter.author": `AuthorYaml`,
"MarkdownRemark.frontmatter.issue": `IssueYaml`,
},
};
My gatsby-node:
const _ = require('lodash');
const fs = require("fs")
const yaml = require("js-yaml")
// Create pages from markdown files
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const ymlDoc = yaml.safeLoad(fs.readFileSync("./src/data/author.yaml", "utf-8"))
const ymlIssueDoc = yaml.safeLoad(fs.readFileSync("./src/data/issue.yaml", "utf-8"))
return new Promise((resolve, reject) => {
resolve(
graphql(
`
query {
fictionarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/fiction/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
nonfictionarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/non-fiction/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
futurearchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/letters/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
issuesarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
authorarchive: allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/" } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
frontmatter {
category
featured
path
title
date(formatString: "DD MMMM YYYY")
}
excerpt
}
}
}
}
`,
).then((result) => {
ymlDoc.forEach(element => {
createPage({
path: element.idpath,
component: require.resolve("./src/templates/eachauthor.js"), /*creates INDIVIDUAL AUTHOR PAGES*/
context: {
idname: element.id,
bio: element.bio,
twitter: element.twitter,
picture: element.picture,
stories: element.stories,
},
});
});
ymlIssueDoc.forEach(element => {
createPage({
path: element.idpath,
component: require.resolve("./src/templates/eachissue.js"), /*creates INDIVIDUAL ISSUE PAGES*/
context: {
issueidname: element.id,
text: element.text,
currentcover: element.currentcover,
artist: element.artist,
artistbio: element.artistbio,
artistimage: element.artistimage,
},
});
});
result.data.fictionarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL FICTION PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.nonfictionarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL NON-FICTION PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.futurearchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL LETTER PAGES*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
result.data.issuesarchive.edges.forEach(({ node }) => {
const component = path.resolve('src/templates/eachpost.js'); /*creates INDIVIDUAL ISSUE PAGES; change template to change every issue page*/
createPage({
path: node.frontmatter.path,
component,
context: {
id: node.id,
},
});
});
const FICposts = result.data.fictionarchive.edges /*creates FICTION LIST PAGES*/
const FICpostsPerPage = 10
const FICnumPages = Math.ceil(FICposts.length / FICpostsPerPage)
Array.from({ length: FICnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/fiction` : `/fiction/${i + 1}`,
component: path.resolve('src/templates/fictionarchive.js'),
context: {
limit: FICpostsPerPage,
skip: i * FICpostsPerPage,
FICnumPages,
FICcurrentPage: i + 1,
},
});
});
const NONFICposts = result.data.nonfictionarchive.edges /*creates NON-FICTION LIST PAGES*/
const NONFICpostsPerPage = 10
const NONFICnumPages = Math.ceil(NONFICposts.length / NONFICpostsPerPage)
Array.from({ length: NONFICnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/non-fiction` : `/non-fiction/${i + 1}`,
component: path.resolve('src/templates/nonfictionarchive.js'),
context: {
limit: NONFICpostsPerPage,
skip: i * NONFICpostsPerPage,
NONFICnumPages,
NONFICcurrentPage: i + 1,
},
});
});
const FUTposts = result.data.futurearchive.edges /*creates LETTERS FROM THE FUTURE LIST PAGES*/
const FUTpostsPerPage = 10
const FUTnumPages = Math.ceil(FUTposts.length / FUTpostsPerPage)
Array.from({ length: FUTnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/future` : `/future/${i + 1}`,
component: path.resolve('src/templates/futurearchive.js'),
context: {
limit: FUTpostsPerPage,
skip: i * FUTpostsPerPage,
FUTnumPages,
FUTcurrentPage: i + 1,
},
});
});
const FULLposts = result.data.issuesarchive.edges /*creates ISSUES LIST PAGES*/
const FULLpostsPerPage = 10
const FULLnumPages = Math.ceil(FULLposts.length / FULLpostsPerPage)
Array.from({ length: FULLnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/fullissues` : `/fullissues/${i + 1}`,
component: path.resolve('src/templates/issuesarchive.js'),
context: {
limit: FULLpostsPerPage,
skip: i * FULLpostsPerPage,
FULLnumPages,
FULLcurrentPage: i + 1,
},
});
});
const AUTposts = result.data.authorarchive.edges
const AUTpostsPerPage = 10
const AUTnumPages = Math.ceil(AUTposts.length / AUTpostsPerPage)
Array.from({ length: AUTnumPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/contributors` : `/contributors/${i + 1}`,
component: path.resolve('src/templates/authorarchive.js'),
context: {
limit: AUTpostsPerPage,
skip: i * AUTpostsPerPage,
AUTnumPages,
AUTcurrentPage: i + 1,
},
});
});
resolve();
}),
);
});
};
My front page (shortened):
import { graphql, withPrefix, Link } from 'gatsby';
import Image from "gatsby-image";
import Helmet from 'react-helmet';
import SEO from '../components/SEO';
import Layout from '../layouts/index';
const Home = (props) => { //THIS SETS THE FRONT PAGE, including featured story, latest stories, and latest issues
const json = props.data.allFeaturesJson.edges;
const posts = props.data.allMarkdownRemark.edges;
return (
<Layout bodyClass="page-home">
<SEO title="Home" />
<Helmet>
<meta
name="Haven Quarterly"
content="A Magazine of Science Fiction and Fantasy"
/>
</Helmet>
{/*FEATURED*/}
<div className="intro pb-1">
<div className="container">
<div className="row2 justify-content-start">
<div className="grid-container pt-2">
<div className="wide">
<div className="col-12">
<Link to="/featured">
<h4>Featured Story</h4>
</Link>
<hr />
</div>
{posts
.filter(post => post.node.frontmatter.featured === true) /*This looks at only the md file with featured: true*/
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue.idpath}> {post.frontmatter.issue.id}</Link></h2> /*THIS IS THE ONLY PLACE ON THIS PAGE WHERE THE ISSUE YAML ARRAY SEEMS TO WORK. IT ONLY WORKS WHEN featured === true WHICH IS CRAZY*/
<p>{post.excerpt}</p>
</div>
)
})}
</div>
<div className="thin">
{posts
.filter(post => post.node.frontmatter.featured === true) /*This looks at only the md file with featured: true*/
.map(({ node: post }) => {
return (
<Link to="/latest">
<Image className="topimage"
fixed={post.frontmatter.currentcover.childImageSharp.fixed} /*This pulls the image from the md file with featured: true (current cover)*/
/>
</Link>
)
})}
</div>
</div>
<hr />
<div className="col-12">
{posts
.filter(post => !post.node.frontmatter.featured)
.filter(post => post.node.frontmatter.issue === "Issue One Summer 2020") /*THIS SHOULD FILTER ONLY MD FILES WITH issue: Issue One Summer 2020"*/
.slice(0, 6)
.map(({ node: post }) => {
return (
<div className="postbody" key={post.id}>
<h2 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link> by <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> ({post.frontmatter.category})
</h2>
</div>
)
})}
</div>
<hr />
</div>
</div>
</div>
<div className="postbody">
<div className="container pt-8 pt-md-4">
<div className="row2 justify-content-start pt-2">
<div className="col-12">
<Link to="/fiction">
<h4>Latest Fiction</h4>
</Link>
<hr />
</div>
{/*FICTION*/}
<div className="container">
{posts
.filter(post => !post.node.frontmatter.featured)
.filter(post => post.node.frontmatter.category === "fiction") /*This should only pull from md files with category "fiction", excluding posts marked featured*/
.slice(0, 6)
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<Image className="inlineimage"
fluid={post.frontmatter.cover.childImageSharp.fluid} /*This should pull image from md files with category "fiction"*/
/>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue}> {post.frontmatter.issue}</Link></h2>
<p>{post.excerpt}</p>
<hr />
</div>
)
})}
<div className="col-12 text-center pb-3">
<Link className="button button-primary" to="/fiction">
View All Stories
</Link>
</div>
</div>
</div>
</div>
</div>
</Layout>
);
};
export const query = graphql`
query {
allAuthorYaml {
nodes {
bio
id
idpath
picture {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
stories {
item
}
twitter
}
}
allIssueYaml {
edges {
node {
artist
artistbio
id
idpath
text
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300, maxHeight: 300) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
allMarkdownRemark(
filter: { fileAbsolutePath: { regex: "/.*.md$/" }}
sort: { fields: [frontmatter___date], order: DESC }
) {
totalCount
edges {
node {
id
frontmatter {
featured
path
title
author {
id
idpath
bio
twitter
picture {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
}
issue {
id
idpath
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
text
artist
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
artistbio
}
date(formatString: "DD MMMM YYYY")
category
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
cover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
excerpt(pruneLength: 650)
}
}
}
allFeaturesJson {
edges {
node {
id
title
description
image
}
}
}
}
`;
export default Home;
A typical template with broken array:
import { graphql, Link, withPrefix } from 'gatsby';
import SEO from '../components/SEO';
import Layout from '../layouts/index';
import Helmet from 'react-helmet';
import Image from 'gatsby-image';
export default class Fictionarchive extends React.Component {
render() {
const posts = this.props.data.allMarkdownRemark.edges
const json = this.props.data.allFeaturesJson.edges;
const { FICcurrentPage, FICnumPages } = this.props.pageContext
const isFirst = FICcurrentPage === 1
const isLast = FICcurrentPage === FICnumPages
const prevPage = FICcurrentPage - 1 === 1 ? "/" : `/fiction/${FICcurrentPage - 1}`
const nextPage = `/fiction/${FICcurrentPage + 1}`
return (
<Layout bodyClass="page-home">
<SEO title="Fiction" />
<Helmet>
<meta
name="description"
content="all fiction of Haven Quarterly"
/>
</Helmet>
<div className="postbody">
<div className="container pt-md-5">
<div className="row2 justify-content-start">
<div className="col-12">
<h3>Latest Fiction</h3>
<hr />
</div>
<div className="container">
{posts
.filter(post => post.node.frontmatter.category === "fiction")
.map(({ node: post }) => {
return (
<div className="container" key={post.id}>
<Image className="inlineimage"
fluid={post.frontmatter.cover.childImageSharp.fluid}
/>
<h1 pb>
<Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
</h1>
<h2>By <Link to={post.frontmatter.author.idpath}> {post.frontmatter.author.id}</Link> in <Link to={post.frontmatter.issue.idpath}> {post.frontmatter.issue.id}</Link></h2>
<p>{post.excerpt}</p>
<hr />
</div>
)
})}
<div className="container">
<div className="row">
<div className="col-sm">
<p className="text-left">
{!isFirst && (
<Link to={prevPage} rel="prev">
← Previous Page
</Link>
)}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Layout>
)
}
}
export const fictionarchiveQuery = graphql`
query fictionarchiveQuery($skip: Int!, $limit: Int!) {
allMarkdownRemark(
filter: { frontmatter: {category:{eq:"fiction"} } }
sort: { fields: [frontmatter___date], order: DESC }
limit: $limit
skip: $skip
) {
edges {
node {
excerpt(pruneLength: 750)
frontmatter {
category
featured
path
title
author {
id
idpath
bio
twitter
picture {
childImageSharp {
fixed(width: 400) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 400, maxHeight: 400) {
...GatsbyImageSharpFluid
}
}
}
}
issue {
id
idpath
currentcover {
childImageSharp {
fixed(width: 403) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
text
artist
artistimage {
childImageSharp {
fixed(width: 200) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 150, maxHeight: 150) {
...GatsbyImageSharpFluid
}
}
}
artistbio
}
date(formatString: "DD MMMM YYYY")
cover {
childImageSharp {
fixed(width: 322) {
...GatsbyImageSharpFixed
}
fluid(maxWidth: 450) {
...GatsbyImageSharpFluid
}
}
}
}
html
}
}
}
allFeaturesJson {
edges {
node {
id
title
description
image
}
}
}
}
`
Solution
Be smarter than me and make sure you've actually defined all of the values you have in your markdown files in your yaml file.
Longer Explanation
I asked on the GitHub repository for Gatsby, and the solution turned out to be really easy:
Everything was set up more or less correctly. The problem turned out to be that in my second yaml file, I wasn't defining all of the values that I was listing in my markdown files. So, between the 30+ .md files, I had four values total for the issue field, but in my issue.yaml file, I was only defining two of them. It returned a null error because when it cycled through the markdown files, it said, "I have this third value, but nothing that corresponds to it in the yaml file."
The person who answered my question said there were two solutions:
Define all issue IDs in issue.yaml so that you get at least the id
back
Define an alternative issueTitle in your frontmatter of the posts
and check whether issue returns null. If yes, hide your components and
use issueTitle instead
I thought this didn't matter because I thought I was only calling data from markdown files with the fields I had defined (in other words, I thought I was already excluding those .md files that I hadn't defined in my yaml), but that was erroneous.
Other Things to Note
Two other simple issues that tripped me up: 1. Make sure that the spelling is exactly the same between what's defined in the yaml file and what is provided in the corresponding field of the markdown file. 2. Make sure you use an id field, as per this suggestion, when mapping your yaml file to a frontmatter field.
Final Thoughts
Turns out I don't know what I'm doing. But I hope that this answer helps anyone else noodling around with Gatsby who gets stuck on the same problem.
Link to the fuller answer kindly provided by LekoArts: https://github.com/gatsbyjs/gatsby/issues/25373

Reactjs not updating the new value of product_number

Reactjs not updating the new value of product_number. I knew that similar question has been asked but am having hard time trying to resolve this.
The Reactjs code below displays provisions records from the arrays.
Now I need to update and replace the value the product_number from 001 to 006.
To this effect, I have added an update button which fetch the product_number from the Axios Call.
My problem is that product_number is not updated with 006 when the button is clicked.
Here is the json response of Axios Call for product_number updates
product_number.json
[{"product_number":"006"}]
Here is the code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import axios from 'axios';
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: false
};
}
componentDidMount() {
this.setState({
data: [
{"provision_id":"1","provision":"Milk","category":[{"category_id":"1","category_price":"100 USD","product":[{"product_id":"1","product_number":"001"}] }]}
],
});
}
// Get and update New Product number of Milk
handleNewProductNumber(prod_id) {
alert(prod_id);
const prod_data = {
prod_id: prod_id};
axios
.get("http://localhost/provision/product_number.json", { prod_data })
.then(response => {
const newData = this.state.data.map(store => {
//if (product.product_id !== prod_id) return product;
return {
...store,
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
};
});
this.setState(state => ({
data: newData
}));
console.log(response.data[0].category_price);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<span>
<label>
<ul>
{this.state.data.map((store) => {
return (
<div key={store.provision_id}>
<div><h1>Provision Store</h1> <br />
<b> Product: </b>{store.provision}
</div>
{store.category && store.category.map((cat) => {
return (
<div key={cat.category_id}>
<div><b>Prices:</b> {cat.category_price}
</div>
{cat.product && cat.product.map((prod) => <div key={prod.product_id}>
<b>Product Number:</b> #{prod.product_number}
<br />
<input
type="button"
value="Get & Update New Product Number"
onClick={() => this.handleNewProductNumber(prod.product_id)}
/>
</div>)}
</div>
)
})}
</div>
)
})}
</ul>
</label>
</span>
);
}
}
Updated Section using map function
return {
...store,
category: store.category.map(
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
})
})
};
The problem is the same of the other question, you have an array of object, with inside another array of objects, in your state:
data: [
{
"provision_id": "1",
"provision": "Milk",
"category": [
{
"category_id": "1",
"category_price": "100 USD",
"product": [
{
"product_id": "1",
"product_number": "001"
}
]
}
]
}
]
To update the inner level, you have to traverse all the state tree:
return {
...store,
category: [{
...store.category,
product: [{
...store.category[0].product,
product_number: response.data[0].product_number
}]
}]
};
Edit after... well, your edit
Your updated piece of code isn't valid syntax:
return {
...store,
category: store.category.map(
product: store.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
})
};
The first store.category.map call takes a function which will be called with a single category as an argument.
You have to spread the category prior to shadow the product property:
return {
...store,
category: store.category.map(
category => ({
...category,
product: category.product.map(
product => {
if (product.product_id !== prod_id) return product
return { ...product, product_number: response.data[0].product_number }
}
)
})
)
};

React mapping an array

I've a object:
{
"products": [
{
"id": 1,
"category": "Milk & meat",
"products": {
"product1": ["Name", "Recipe", "Photo"]
}
}
}
and it's mapped like that:
return (
<div className="box list">
{this.props.products
.map((product, ind) =>
<div key={ind}>
<h2>{product.category}</h2>
{Object.values(product.products).map(name => <li onClick={this.props.handleClickedProduct}>{name[0]}</li>)}
</div>)}
</div>
)
The onClick method passes the product name ([0] in array) to other component. It does it like that:
handleClickedProduct = (e) => {
this.setState({ clickedProduct: e.target.innerHTML });
}
How cane I setState [1] and [2] from the same array?
I want to pass forward the product name and keep in state the recipe and photo.
Thanks,
Kuba
class Example extends React.Component {
products = [
{
id: 1,
category: "Milk & meat",
products: {
product1: ["Name", "Recipe", "Photo"]
}
},
{
id: 2,
category: "Milk & bread",
products: {
product1: ["Name", "Recipe", "Photo"]
}
}
]
state = {
clickedProduct: null
}
handleClick = (product) => (e) => {
this.props.onClick(product)
}
render() {
return (
<div className="box list">
{this.products.map((product, ind) =>
<div key={ind}>
<h2>{product.category}</h2>
<ul>
{Object.values(product.products)
.map(pr => <li onClick={this.handleClick(pr)}>{pr[0]}</li>)}
</ul>
</div>)
}
</div>
)
}
}
class Handler extends React.Component {
handler = (e) => {
console.log(e)
}
render () {
return <Example onClick={this.handler}/>
}
}
ReactDOM.render(
<Handler />,
document.getElementById('container')
);
Change handleClickedProduct to be
handleClickedProduct = (name) => (e) => {
this.setState({ clickedProduct: e.target.innerHTML });}
and inside your map you can just do this.handleClickedProduct(product.products) and use it inside the handleClickedProduct function.

Resources