GatsbyJS getting data from Restful API - reactjs

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
}
}
}
}
}

Related

Crate Tag & Category taxonomy in Gatsby

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.

How to add meta tags with gatsby which first need to be fetched

I'm trying to add META tags for Facebook(og-tags). I'm using Gatsby and Helmet. But the problem is that those tags first need to be fetched.
It's a vehicle detail page and I need to show vehicle make and model in those tags, but the vehicle needs first to be fecthed. My code is as follows:
import Helmet from 'react-helmet';
const Page = (props) => {
const [detailsMeta, setDetailsMeta] = useState(undefined);
const resolveVehicleDetailMeta = async () => {
const fetch = require('isomorphic-fetch');
const resolveVehicleImageUrl = (fetchedImage) => {
const parsed = JSON.parse(fetchedImage);
return parsed?.uri
}
const VEHICLE_QUERY = `
query VehicleQuery($reference: String!) {
vehicle (reference: $reference) {
reference
make
model
image
}
}`;
await fetch(`/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: VEHICLE_QUERY,
variables: {
reference: 'some reference'
}
})
})
.then((resp) => resp.json())
.then((result) => {
const vehicle = result?.data?.vehicle;
if(vehicle){
setDetailsMeta({
reference: vehicle.reference,
make: vehicle.make,
model: vehicle.model,
image: resolveVehicleImageUrl(vehicle.image)
})
}
})
.catch((err) => {
console.log('err', err)
});
}
const renderMetaTitle = () => {
const ogTitle = `Tweedehands ${detailsMeta?.make} ${detailsMeta?.model} ${detailsMeta?.reference}`
return ogTitle;
}
return (
<>
<Helmet>
{detailsMeta && <meta property='og:title' content={renderMetaTitle()} />}
...
</Helmet>
The rest...
</>
)
}
And in gatsby config is gatsby-plugin-react-helmet added. The config file is as follows:
const plugins = [
`gatsby-plugin-react-helmet`,
....
]
module.exports = {
developMiddleware: (app) => {
app.use(
'/graphql',
createProxyMiddleware({
target: 'http://localhost:8000'
})
);
},
siteMetadata: {
siteUrl: `https://${settings.DOMAIN}`
},
plugins: plugins
};
Thus, I first fetch data from the server and store it in detailsMeta and then I show it inside Helmet. When I test it on localhost I see those tags and it works fine, but when I test it in Facebook debugger they are not shown.
Can I at all add data to the og-tags which first needs to be fetched and that it be seen by Facebook?
Thanks.
Can I at all add data to the og-tags which first needs to be fetched
and that it be seen by Facebook?
Yes but only if you are using statically analyzed data (i.e: using page queries, static queries, etc). In that case, you just need to add gatsby-plugin-react-helmet plugin in order to add drop-in server-side support to Helmet component.
In your gatsby-config.js:
plugins: [`gatsby-plugin-react-helmet`]
Extracted from https://www.gatsbyjs.com/plugins/gatsby-plugin-react-helmet/
In your case, you are using the fetch method that will be fired on the client-side, so the data won't be statically analyzed hence not present when the Facebook crawler reaches the site. This means that the Helmet component will be populated later than the crawler requests it.
I'm not sure about your specs but you can try converting your fetched into GraphQL nodes in order to use pages queries or static queries fulfill the Helmet component properly.

ApolloClient v3 fetchMore with nested query results

I'm using ApolloClient 3 the GitHub GraphQL API to retrieve all releases from a repo.
This is what the query looks like:
query ($owner: String!, $name: String!, $first: Int, $after: String, $before: String) {
repository(owner: $owner, name: $name) {
id
releases(orderBy: {field: CREATED_AT, direction: DESC}, first: $first, after: $after, before: $before) {
nodes {
name
publishedAt
resourcePath
tagName
url
id
isPrerelease
description
descriptionHTML
}
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
}
This is what the result payload looks like:
This returns me the first x entries (nodes). So far, all good.
I need to implement pagination and I make use of the fetchMore function provided by ApolloClient useQuery. Calling fetchMore fetches the next x entries successfully but these are not displayed in my component list.
According to the ApolloClient Pagination documentation, it seems necessary to handle the merging of the fetchMore results with the ApolloClient caching mechanism. The documentation is understandable for simple situations but I am struggling to implement a solution for the situation where the actual array of results that needs to be merged togeher is deeply nested in the query result (repository -> releases -> nodes).
This is my implementation of the InMemoryCache options merge:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
ReleaseConnection: {
fields: {
nodes: {
merge(existing, incoming, options) {
const previous = existing || []
const results = [...previous, ...incoming]
return results
}
}
}
},
}
}
The results array here contains the full list, including the existing entries and the new x entries. This is essentially the correct result. However, my component list which is using the useQuery and fetchMore functionality does not get the new entries after the fetchMore is called.
I have tried various combinations in the inMemoryCacheOptions code above but so far I have been unsuccessful.
To add more context, this is the related component code:
export default function Releases() {
const { loading, error, data, fetchMore } = useQuery(releasesQuery, {
variables: {
owner: "theowner",
name: "myrepo",
first: 15
}
});
if (loading) return null;
if (error) {
console.error(error);
return null;
}
if (data) {
console.log(data?.repository?.releases?.pageInfo?.endCursor);
}
const handleFetchMore = () => {
fetchMore({
variables: {
first: 15,
after: data?.repository?.releases?.pageInfo?.endCursor
}
});
};
return (
<div>
<ul>
{data?.repository?.releases?.nodes?.map(release => (
<li key={release.id}>{release.name}</li>
))}
</ul>
<button onClick={handleFetchMore}>Fetch More</button>
</div>
);
}
After fetchMore the component doesn't rerender with the new data.
If anyone has any other ideas that I could try, I'd be grateful.
I finally managed to solve this. There was no change to the react component code but the InMemoryCacheOptions now looks like this:
const inMemoryCacheOptions = {
addTypename: true,
typePolicies: {
Repository: {
fields: {
releases: {
keyArgs: false,
merge(existing, incoming) {
if (!incoming) return existing;
if (!existing) return incoming;
const { nodes, ...rest } = incoming;
// We only need to merge the nodes array.
// The rest of the fields (pagination) should always be overwritten by incoming
let result = rest;
result.nodes = [...existing.nodes, ...nodes];
return result;
}
}
}
}
}
};
The main change from my original code is that I now define the typePolicy for the releases field of the Repository type. Previously I was trying to get directly to the nodes field of the Release type. Since my Repository type the root of the gql query and used in the component, it now reads the merged results from the cache.
If I specified the typePolicy for Query as mentioned in the docs, I would not be able to specify the merge behaviour for the releases field because it would be one level too deep (i.e. Query -> repository -> releases). This is what lead to my confusion in the beginning.

Getting data Gatsby.js and contentful

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.

TypeError: result.data.umdHub.articles.forEach is not a function

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

Resources