I have pages in contenful with different URLs. Now I'm getting all data from all pages, but I need to get different data for different URL. I'm tryin to filter it, but get error. So How I can check if url='something' I need query it ?
import React from "react";
import { StaticQuery, graphql } from "gatsby";
import ArticleMfo from "../components/articleMfo";
const Products = () => (
<StaticQuery
query={graphql`
query MyQuery {
allContentfulAllPages(filter: {link: {eq: $MYURL}}) {
edges {
node {
mfo {
__typename
... on ContentfulBank {
id
text
limit
rate
term
link
logo {
title
file {
url
}
}
}
}
}
}
}
}
`}
render={data => (
<Container className="container">
{data.allContentfulAllPages.edges.map(({ node }, i) => (
<div>
{node.mfo.map(mfos => (
<ArticleMfo key={mfos.id} content={mfos} />
))}
</div>
))}
</Container>
)}
/>
);
export default Products
Static query (hence the name) does not accept variables. As you can see from the Static Query docs:
StaticQuery does not accept variables (hence the name “static”), but
can be used in any component, including pages
If you want to filter it, you will need to use a page query and pass the variable name (MYURL) via context on each page. In that case, you'll need to move your query to gatsby-node.js and, on every page creation, pass the variable through context to make it available to use as a filter. Something like:
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(
`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
path
}
}
}
}
}
`
)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const path = node.frontmatter.path
createPage({
path,
component: blogPostTemplate,
// In your blog post template's graphql query, you can use pagePath
// as a GraphQL variable to query for data from the markdown file.
context: {
pagePath: path,
},
})
})
}
Note: Replace the query above and the resolvers for your data.
With the snippet above, every page created from the GraphQL query will have the path available (as pagePath) through context to filter, adapt it to your needs.
Related
I have a development on Gatsby with allMdx. I created a "Category" taxonomy and to create a category page I used a file gatsby-node.js . There's such a code inside.
const _ = require("lodash")
const { transliterate } = require('./src/functions/transletter');
function dedupeCategories(allMdx) {
const uniqueCategories = new Set()
// Iterate over all articles
allMdx.edges.forEach(({ node }) => {
// Iterate over each category in an article
node.frontmatter.categories.forEach(category => {
uniqueCategories.add(category)
})
})
// Create new array with duplicates removed
return Array.from(uniqueCategories)
}
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
// Query markdown files including data from frontmatter
const { data: { allMdx } } = await graphql(`
query {
allMdx {
edges {
node {
id
frontmatter {
categories
tags
slug
}
}
}
}
}
`)
// Create array of every category without duplicates
const dedupedCategories = dedupeCategories(allMdx)
// Iterate over categories and create page for each
dedupedCategories.forEach(category => {
reporter.info(`Creating page: blog/category/${category}`)
createPage({
path: `blog/category/${_.kebabCase(transliterate(category))}`,
component: require.resolve("./src/templates/categories.js"),
// Create props for our CategoryList.js component
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
})
})
}
Now I want to create a "Tag" taconomy, but I can't figure out how to do it beautifully and briefly, what and where to add to the gatsby-node.js so that I have two taxonomies created that work the same way as one. It is clear that you can simply duplicate this code and write "tag" instead of "category", but this is not very nice.
Just in case, here is my template code category.js
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from '../components/layout'
import Seo from '../components/seo'
const CategoryList = ({ pageContext: { category }, data: { allMdx }, }) =>
(
<Layout pageTitle={category}>
{
allMdx.edges.map(({ node }) => {
return (
<article key={node.id}>
<h2>
<Link to={`/blog/${node.frontmatter.slug}`}>
{node.frontmatter.title}
</Link>
</h2>
<p>Posted: {node.frontmatter.date}</p>
<p>{node.excerpt}</p>
</article>
)
})
}
</Layout>
)
export const query = graphql`
query CategoryListQuery($ids: [String]!) {
allMdx (filter: { id: { in: $ids } }) {
edges {
node {
frontmatter {
title
date(formatString: "MMMM DD, YYYY")
slug
}
id
excerpt
}
}
}
}
`
export const Head = ({ pageContext }) => (
<Seo
title={pageContext.category}
description={`Статьи из категории ${pageContext.category}`}
/>
)
export default CategoryList
I don't see anything wrong in your approach. In fact, is the way to go in terms of getting all tags and categories (hence a map) and loop through them to create the pages, at least given your approach.
However, I think you can save some steps if you change your markdown structure: if each MDX has a key attribute (or similar) containing the type of taxonomy it is (article, tag, category, page, etc) you can create more succinct GraphQL queries, hence you can save the filters.
For instance, in that way, you would be able to create a single query tags and categories:
const tagsQuery = await graphql(`
query getAllTags {
allTags: allMarkdownRemark (
filter: { frontmatter: { key: { in: ["tag"] }}}) {
edges {
node {
id
frontmatter {
name
slug
type
key
}
}
}
}
}
`);
Note: you can use eq operator instead of in. In this case the array will accept more types of tags like projectTags, articleTags, etc.
This allows you to create more specific approach because your data will contain allTags and allCategories, so you can create a general dedupeCategories (which will be named dedupeMdx) which no matter the input, will return a unique array (of tags or categories) because you don't care about the data, all MDX will have the same internal structure to the loop and the function can be agnostic to that.
Following that approach, you can omit the following filter:
context: {
category,
// Create an array of ids of articles in this category
ids: allMdx.edges
.filter(({ node }) => {
return node.frontmatter.categories.includes(category)
})
.map(({node}) => node.id),
},
The filter won't be necessary if you pass the dedupedCategories array (and so with the tags) and use a filter GraphQL in the template query, which in fact is what you would do either way, so you are saving one step.
In other words, you create pages for each category (or tag), pass the array of categories via context and get, from allMdx (filtered by the key + the unique array) the needed data.
I'm prototyping a project using NextJS, Prisma, and ClerkJS. I'm trying to understand how I would supply various params/props to my Prisma search clause. In particular I need to get the email address of a user from ClerkJS. This is my current index file:
import React from "react";
import prisma from "../../prisma/initPrisma"
const FacilitiesPage = ({ facilities }) => {
return (
<div className={styles.dashCards}>
{facilities.map((facility) => {
return (
<div className={styles.card} key={facility.id}>
<h4>{facility.name}</h4>
</div>
);
})}
</div>
);
};
export async function getStaticProps() {
const facilities = await prisma.facility.findMany({
where: {
ownerEmail: 'harcodedemail'
},
});
return {
props: {
facilities,
},
};
}
export default FacilitiesPage;
Obviously I can't hardcode the email address of every user in the system. ClerkJS offers several ways to query the user object and return various things from it, which I could pass into getStaticProps (or getServerSideProps probably). But nothing I've tried works. Candidly, I'm still learning the "React way" to do a lot of things.
TL;DR: how do I supply props to the query string in getStaticProps?
The folks at Clerk.dev just answered this question. You need to use getServerSideProps and then use the new "withServerSideAuth" component. Here is a snippet from the blog post https://clerk.dev/blog/next-js-ssr-authentication-with-clerk :
import { withServerSideAuth } from "#clerk/nextjs/ssr";
export const getServerSideProps = withServerSideAuth(async ({ req, resolvedUrl }) => {
const {sessionId,getToken} = req.auth;
if (!sessionId) {
return { redirect: { destination: "/sign-in?redirect_url=" + resolvedUrl } };
}
// use a token for your Clerk integrations
const hasuraToken = await getToken({ template: 'hasura' });
// retrieve data from your Hasura integration
return { props: {} };
});
If I have a simple UI component that displays some some product info. And it contains it's own fragment like so:
const Product = ({detail}) => {
const data = useFragment(
graphql`
fragment Product_detail on Product {
title
detail
}
`,
detail
)
return (
<div>
<h1>{data.title}</h1>
<p>{data.detail}</p>
</div>
}
And it is use it inside a parent component that looks like this:
const Page1 = ({content}) => {
const data = useFragment(
graphql`
fragment Page1_content on Page1Info {
oneProduct {
...Product_detail
}
}
`,
detail
)
return <Product detail={data.oneProduct}/>
}
How could I reuse the Product component on a different page, where the parent's graphql fragment returns an array of Products, instead of just one product?
const Page2 = ({content}) => {
const data = useFragment(
graphql`
fragment Page2_content on Page2Info {
allProducts { //returns an array of products
title
...Product_detail
}
}
`,
detail
)
return (
<div>
{data.allProducts.map(product => <button>{product.name}</button>)}
{*/Display the product detail of the product who's button is clicked*/}
<Product detail={data.allProducts}/>
</div>
}
I am using Relay as my graphql client. But I'm assuming the principal are the same across all clients?
I am having issues linking pages with slugs. All I am aiming to do is create a page with a list of articles (which I have). But I cannot link those articles to display their content. I understand you might need to use createPages. Below is the code I am trying. Does anyone have experience with this that might be able to point me in the right direction for linking index and article pages?
exports.createPages = ({ graphql, actions }) => {
// **Note:** The graphql function call returns a Promise
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more info
const { createPage } = actions
return graphql(`
{
umdHub {
articles {
data {
slug
title
body
subtitle
hero_image {
url_1200_630
}
}
}
}
}
`
).then(result => {
result.data.umdHub.articles.forEach(({ data }) => {
createPage({
path: articles.data.slug,
component: path.resolve(`./src/article.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: articles.data.slug,
},
})
})
})
}
I am getting this error with the above code:
TypeError: result.data.umdHub.articles.forEach is not a function
Second Attempt:
const path = require(`path`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const articleTemplate = path.resolve(`./src/terp/article.js`)
// Query for markdown nodes to use in creating pages.
// You can query for whatever data you want to create pages for e.g.
// products, portfolio items, landing pages, etc.
return graphql(`
{
umdHub {
articles {
data {
id
title
subtitle
body
summary
hero_image {
url_1200_630
}
authorship_date {
formatted_short
unix
unix_int
formatted_long
formatted_short
time
}
slug
}
}
}
}
`).then(result => {
if (result.errors) {
throw result.errors
}
// Create blog post pages.
result.data.umdHub.articles.data.forEach(data => {
createPage({
// Path for this page — required
path: `${data.slug}`,
component: articleTemplate,
context: {
// Add optional context data to be inserted
// as props into the page component..
//
// The context data can also be used as
// arguments to the page GraphQL query.
//
// The page "path" is always available as a GraphQL
// argument.
},
})
})
})
}
Returns error:
⠐ createPages Your site's "gatsby-node.js" created a page with a
component that doesn't exist
I am new in both React and GatsbyJS. I am confused and could not make figuring out in a simple way to load data from third-party Restful API.
For example, I would like to fetch data from randomuser.me/API and then be able to use the data in pages.
Let’s say something like this :
import React from 'react'
import Link from 'gatsby-link'
class User extends React.Component {
constructor(){
super();
this.state = {
pictures:[],
};
}
componentDidMount(){
fetch('https://randomuser.me/api/?results=500')
.then(results=>{
return results.json();
})
.then(data=>{
let pictures = data.results.map((pic,i)=>{
return(
<div key={i} >
<img key={i} src={pic.picture.medium}/>
</div>
)
})
this.setState({pictures:pictures})
})
}
render() {
return (<div>{this.state.pictures}</div>)
}
}
export default User;
But I would like to get the help of GraphQL in order to filter & sort users and etc…..
Could you please help me to find the sample to how I can fetch data and insert them into GraphQL on gatsby-node.js?
If you want to use GraphQL to fetch your data, you have to create a sourceNode. The doc about creating a source plugin could help you.
Follow these steps to be able to query randomuser data with GraphQL in your Gatsby project.
1) Create nodes in gatsby-node.js
In your root project folder, add this code to gatsby-node.js:
const axios = require('axios');
const crypto = require('crypto');
exports.sourceNodes = async ({ actions }) => {
const { createNode } = actions;
// fetch raw data from the randomuser api
const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
const res = await fetchRandomUser();
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last,
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail,
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto
.createHash(`md5`)
.update(JSON.stringify(userNode))
.digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
return;
}
I used axios to fetch data so you will need to install it: npm install --save axios
Explanation:
The goal is to create each node for each piece of data you want to use.
According to the createNode documentation, you have to provide an object with few required fields (id, parent, internal, children).
Once you get the results data from the randomuser API, you just need to create this node object and pass it to the createNode() function.
Here we map to the results as you wanted to get 500 random users https://randomuser.me/api/?results=500.
Create the userNode object with the required and wanted fields.
You can add more fields depending on what data you will want to use in your app.
Just create the node with the createNode() function of the Gatsby API.
2) Query your data with GraphQL
Once you did that, run gatsby develop and go to http://localhost:8000/___graphql.
You can play with GraphQL to create your perfect query. As we named the internal.type of our node object 'RandomUser', we can query allRandomUser to get our data.
{
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
3) Use this query in your Gatsby page
In your page, for instance src/pages/index.js, use the query and display your data:
import React from 'react'
import Link from 'gatsby-link'
const IndexPage = (props) => {
const users = props.data.allRandomUser.edges;
return (
<div>
{users.map((user, i) => {
const userData = user.node;
return (
<div key={i}>
<p>Name: {userData.name.first}</p>
<img src={userData.picture.medium} />
</div>
)
})}
</div>
);
};
export default IndexPage
export const query = graphql`
query RandomUserQuery {
allRandomUser {
edges {
node {
gender
name {
title
first
last
}
picture {
large
medium
thumbnail
}
}
}
}
}
`;
That is it!
Many thanks, this is working fine for me, I only change small parts of the gastbyjs-node.js because it makes an error when use sync & await, I think I need change some section of a build process to use babel to allow me to use sync or await.
Here is the code which works for me.
const axios = require('axios');
const crypto = require('crypto');
// exports.sourceNodes = async ({ boundActionCreators }) => {
exports.sourceNodes = ({boundActionCreators}) => {
const {createNode} = boundActionCreators;
return new Promise((resolve, reject) => {
// fetch raw data from the randomuser api
// const fetchRandomUser = () => axios.get(`https://randomuser.me/api/?results=500`);
// await for results
// const res = await fetchRandomUser();
axios.get(`https://randomuser.me/api/?results=500`).then(res => {
// map into these results and create nodes
res.data.results.map((user, i) => {
// Create your node object
const userNode = {
// Required fields
id: `${i}`,
parent: `__SOURCE__`,
internal: {
type: `RandomUser`, // name of the graphQL query --> allRandomUser {}
// contentDigest will be added just after
// but it is required
},
children: [],
// Other fields that you want to query with graphQl
gender: user.gender,
name: {
title: user.name.title,
first: user.name.first,
last: user.name.last
},
picture: {
large: user.picture.large,
medium: user.picture.medium,
thumbnail: user.picture.thumbnail
}
// etc...
}
// Get content digest of node. (Required field)
const contentDigest = crypto.createHash(`md5`).update(JSON.stringify(userNode)).digest(`hex`);
// add it to userNode
userNode.internal.contentDigest = contentDigest;
// Create node with the gatsby createNode() API
createNode(userNode);
});
resolve();
});
});
}
The accepted answer for this works great, just to note that there's a deprecation warning if you use boundActionCreators. This has to be renamed to actions to avoid this warning.
You can get data at the frontend from APIs using react useEffect. It works perfectly and you will no longer see any error at builtime
const [starsCount, setStarsCount] = useState(0)
useEffect(() => {
// get data from GitHub api
fetch(`https://api.github.com/repos/gatsbyjs/gatsby`)
.then(response => response.json()) // parse JSON from request
.then(resultData => {
setStarsCount(resultData.stargazers_count)
}) // set data for the number of stars
}, [])
The answers given above work, except the query in step 2 seems to only return one node for me. I can return all nodes by adding totalCount as a sibling of edges. I.e.
{
allRandomUser {
totalCount
edges {
node {
id
gender
name {
first
last
}
}
}
}
}