I would like to nest two queries on my Gatsby index.js page. I have front matter on my posts that includes the name and directory of the featured image. Query #1 is to retrieve those strings and query #2 is to build a childImageSharp using a combination of those strings.
What I have tried is:
import React from "react"
import { css } from "#emotion/core"
import { graphql, compose } from "gatsby"
import { rhythm } from "../utils/typography"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
function Home({ mdHeaderData, introImage }) {
return (
<div>
{mdHeaderData.allMarkdownRemark.edges.map(({ node }) => (
<h4>node.frontmatter.gallery</h4>
<GatsbyImage image={getImage(introImage.node)} />
</div>
);
};
const mdHeaderQuery = graphql`
query mdHeaderQuery {
allMarkdownRemark {
edges {
node {
id
frontmatter {
gallery
introimage
}
}
}
}
`
const introImageQuery = graphql`
query introImageQuery {
allFile(
filter: {
sourceInstanceName: {eq: "galleries"},
relativePath: {eq: $introImagePath}
}
) {
nodes {
childImageSharp {
gatsbyImageData(
width: 500
)
}
}
}
}
`
export default compose(
graphql(mdHeaderQuery, {
name: 'mdHeaderData'
}),
graphql(introImageQuery, {
name: 'introImage',
options: ({ mdHeaderData }) => ({
variables: {
introImagePath: mdHeaderData.allMarkdownRemark.edges.node.frontmatter.gallery + "/" + mdHeaderData.allMarkdownRemark.edges.node.frontmatter.introimage
}
})
})
) (Home)
I am getting SSR errors saying that graphql is no defined and pointing at the export default compose line.
Related
In Gatsby I've coded a single post template:
singlePost.js:
import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { H1 } from '../elements'
import { Container, Post, FeatureImage } from '../components'
const singlePost = ({ data }) => {
const featureImage = data.mdx.frontmatter.featureImg.childImageSharp.fixed
return (
<Container>
<FeatureImage fixed={featureImage} />
<Post>
<H1 margin=" 0 0 2rem 0">{data.mdx.frontmatter.title}</H1>
<MDXRenderer>{data.mdx.body}</MDXRenderer>
</Post>
</Container>
)
}
export const pageQuery = graphql`
query SinglePostQuery($id: String!) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
}
`
export default singlePost
In my gatsby-node.js I get the data from the slug:
data.allMdx.edges.map(edge => {
const slug = edge.node.frontmatter.slug
const id = edge.node.id
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/singlePost.js`),
context: { id },
})
})
In the frontmatter of the markdown file there is a feature image:
---
title: Bacon Ipsum
slug: bacon
date: 2021-02-09
featureImg: nature.jpg
excerpt: Bacon ipsum dolor amet pastrami prosciutto meatball fatback, andouille drumstick shank burgdoggen brisket cow turkey.
---
if the post markdown file doesn't have an image I get an error of:
Cannot read property 'childImageSharp' of null
I can access the default image I've set with:
const defaultImage = useStaticQuery(graphql`
query {
default: file(relativePath: { eq: "default.jpg" }) {
publicURL
}
}
`)
but when I try to query for the content and the default with:
const defaultImage = useStaticQuery(graphql`
query SinglePostQuery($id: String!) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
default: file(relativePath: { eq: "default.jpg" }) {
publicURL
}
}
`)
I get an error of:
If you're not using a page query but a useStaticQuery / StaticQuery
you see this error because they currently don't support variables.
In Gatsby how can I query the slug but also have pass the default image to the Feature Image component?
As the error log points, useStaticQuery (because it's a type of static query) doesn't accept variables, hence the name.
In Gatsby how can I query the slug but also have pass the default
image to the Feature Image component?
You can use the same context where you attach the id to pass the image while using page queries.
data.allMdx.edges.map(edge => {
const slug = edge.node.frontmatter.slug
const id = edge.node.id
const imagePath = edge.node.featureImg || 'default.jpg'
actions.createPage({
path: slug,
component: require.resolve(`./src/templates/singlePost.js`),
context: {
id
imagePath
},
})
})
Note: You may need to query the image in your gatsby-node.js. Change the imagePath to another identifier that matches better your data structure if needed.
Then, in your template:
import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { H1 } from '../elements'
import { Container, Post, FeatureImage } from '../components'
const singlePost = ({ data }) => {
const featureImage = data.mdx.frontmatter.featureImg.childImageSharp.fixed
return (
<Container>
<FeatureImage fixed={featureImage} />
<Post>
<H1 margin=" 0 0 2rem 0">{data.mdx.frontmatter.title}</H1>
<MDXRenderer>{data.mdx.body}</MDXRenderer>
</Post>
</Container>
)
}
export const pageQuery = graphql`
query SinglePostQuery($id: String!, $imagePath: String) {
mdx(id: { eq: $id }) {
body
frontmatter {
date
excerpt
featureImg {
childImageSharp {
fixed(width: 1920) {
...GatsbyImageSharpFixed
}
}
}
title
slug
}
}
default: file(relativePath: { eq: $imagePath }) {
publicURL
}
}
`
export default singlePost
Note: add the $imagePath as a nullable value (by removing the exclamation mark, !), since, as you said, not all the posts will have it.
Try removing the featureImg block if it stills breaking the query. Because of:
const imagePath = edge.node.featureImg || 'default.jpg'
Your imagePath variable will always contain the needed data,or your featureImg, or your default one. The key is to make separate queries.
As I see there is no json option anymore when querying the contentfulBlogPost only raw. I was able to make some changes to get everything from the body, except the image from that post.
If I made a test in GraphQL Playground I can get the image id and url but that's it.
query {
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
I tried to find an example how to get embedded images but no luck....
import React from 'react'
import { graphql } from 'gatsby'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import Layout from '../components/layout'
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
}
}
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
`
const Blog = (props) => {
const options = {
renderNode: {
"embedded-asset-block": (node) => {
const alt = node.data.title
const url = node.file.url
return <img alt={alt} src={url}/>
}
}
}
return (
<Layout>
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.contentfulBlogPost.body.raw, options))}
</Layout>
)
}
export default Blog
Plugins:
...
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
'gatsby-remark-relative-images',
{
resolve: 'gatsby-remark-images-contentful',
options: {
maxWidth: 750,
linkImagesToOriginal: false
}
}
]
}
}
],
}
Hi I saw this solution in a Youtube comment. First thing you have to do is change your Graphql query to something like this:
query ($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
id
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
references {
... on ContentfulAsset {
contentful_id
title
file {
url
}
}
}
}
}
}
Then change your options constant to:
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
console.log(node);
const imageID = node.data.target.sys.id;
const {
file: {url},
title
} = props.data.contentfulBlogPost.body.references.find(({contentful_id: id}) => id === imageID);
return <img src={url} alt={title} />
}
}
}
Use something like:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
},
}
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
The return statement in BLOCKS.EMBEDDED_ASSET entry will contain your data, adapt to your needs. If you go inside the dependency, you'll see all the exposed methods, so you will have also a BLOCKS.EMBEDDED_ENTRY entry for your embedded entries. Apply it like:
[BLOCKS.EMBEDDED_ENTRY]: node => {
// your logic to manipulate the entry here
return (
<>
<div>whatever</div>
</>
)
},
For anyone that is still struggling to find the "references" field in graphql, remember that you HAVE TO first create an entry in contentful by adding at least one image. Otherwise, the references field will not show up in graphql, hence you can not query it.
I wanna integrate multiple clients in my react-hooks application. I'm using graphql-hooks via Apollo client we have a module to integrate multiple clients
Following is the link Apollo graphql multiple client
`https://www.npmjs.com/package/#titelmedia/react-apollo-multiple-clients`
Following I'm using for graphql hooks
https://www.npmjs.com/package/graphql-hooks
How do we achieve the same for graphql-hooks?
My requirement is depending on the selection of the radio button I need to switch between these multiple clients all that in one component using more than one client.
In my app I'm using graphql-hook we wrap the component to the client here the same component has functionality wherein depending on the select one of the radio buttons the client must be switch.I'm having one client but need to integrate multiple clients I googled but i've not found so have questioned here.
Is this possible?
Can anyone please help out there
We can use new GraphQLClient() multiple times to get multiple graphql clients
Here is a good approach and you can follow it: https://www.loudnoises.us/next-js-two-apollo-clients-two-graphql-data-sources-the-easy-way/
I have two middleware graphql servers running. One for the app one for analytics. I do it in two different ways according to my need as below...
CubeClient.jsx
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { SchemaLink } from "apollo-link-schema";
import { makeExecutableSchema } from "graphql-tools";
const cache = new InMemoryCache();
const defaultDashboardItems = [{"vizState":"{\"query\":{\"measures\":[\"Product.count\"],\"timeDimensions\":[{\"dimension\":\"Product.createdAt\"}],\"dimensions\":[\"Producttype.name\"],\"filters\":[],\"order\":{}},\"chartType\":\"bar\",\"orderMembers\":[{\"id\":\"Product.count\",\"title\":\"Product Count\",\"order\":\"none\"},{\"id\":\"Producttype.name\",\"title\":\"Producttype Name\",\"order\":\"none\"},{\"id\":\"Product.createdAt\",\"title\":\"Product Created at\",\"order\":\"none\"}],\"pivotConfig\":{\"x\":[\"Producttype.name\"],\"y\":[\"measures\"],\"fillMissingDates\":true,\"joinDateRange\":false},\"shouldApplyHeuristicOrder\":true,\"sessionGranularity\":null}","name":"Product Types","id":"2","layout":"{\"x\":0,\"y\":0,\"w\":24,\"h\":8}"},{"vizState":"{\"query\":{\"measures\":[\"Sale.total\"],\"timeDimensions\":[{\"dimension\":\"Sale.createdAt\",\"granularity\":\"day\"}],\"dimensions\":[\"Customer.firstname\"],\"order\":{\"Sale.total\":\"desc\"}},\"chartType\":\"area\",\"orderMembers\":[{\"id\":\"Sale.total\",\"title\":\"Sale Total\",\"order\":\"desc\"},{\"id\":\"Customer.firstname\",\"title\":\"Customer Firstname\",\"order\":\"none\"},{\"id\":\"Sale.createdAt\",\"title\":\"Sale Created at\",\"order\":\"none\"}],\"pivotConfig\":{\"x\":[\"Sale.createdAt.day\"],\"y\":[\"Customer.firstname\",\"measures\"],\"fillMissingDates\":true,\"joinDateRange\":false},\"shouldApplyHeuristicOrder\":true}","name":"Sale Total","id":"3","layout":"{\"x\":0,\"y\":8,\"w\":24,\"h\":8}"}]
export function getCubeClient(location){
const getDashboardItems = () => {
// return JSON.parse(window.localStorage.getItem("dashboardItems")) || defaultDashboardItems;
return defaultDashboardItems
}
const setDashboardItems = items => {
return window.localStorage.setItem("dashboardItems", JSON.stringify(items));
}
const nextId = () => {
const currentId = parseInt(window.localStorage.getItem("dashboardIdCounter"), 10) || 1;
window.localStorage.setItem("dashboardIdCounter", currentId + 1);
return currentId.toString();
};
const toApolloItem = i => ({ ...i, __typename: "DashboardItem" });
const typeDefs = `
type DashboardItem {
id: String!
layout: String
vizState: String
name: String
}
input DashboardItemInput {
layout: String
vizState: String
name: String
}
type Query {
dashboardItems: [DashboardItem]
dashboardItem(id: String!): DashboardItem
}
type Mutation {
createDashboardItem(input: DashboardItemInput): DashboardItem
updateDashboardItem(id: String!, input: DashboardItemInput): DashboardItem
deleteDashboardItem(id: String!): DashboardItem
}
`;
const schema = makeExecutableSchema({
typeDefs,
resolvers: {
Query: {
dashboardItems() {
const dashboardItems = getDashboardItems();
return dashboardItems.map(toApolloItem);
},
dashboardItem(_, { id }) {
const dashboardItems = getDashboardItems();
return toApolloItem(dashboardItems.find(i => i.id.toString() === id));
}
},
Mutation: {
createDashboardItem: (_, { input: { ...item } }) => {
const dashboardItems = getDashboardItems();
item = { ...item, id: nextId(), layout: JSON.stringify({}) };
dashboardItems.push(item);
setDashboardItems(dashboardItems);
return toApolloItem(item);
},
updateDashboardItem: (_, { id, input: { ...item } }) => {
const dashboardItems = getDashboardItems();
item = Object.keys(item)
.filter(k => !!item[k])
.map(k => ({
[k]: item[k]
}))
.reduce((a, b) => ({ ...a, ...b }), {});
const index = dashboardItems.findIndex(i => i.id.toString() === id);
dashboardItems[index] = { ...dashboardItems[index], ...item };
setDashboardItems(dashboardItems);
return toApolloItem(dashboardItems[index]);
},
deleteDashboardItem: (_, { id }) => {
const dashboardItems = getDashboardItems();
const index = dashboardItems.findIndex(i => i.id.toString() === id);
const [removedItem] = dashboardItems.splice(index, 1);
setDashboardItems(dashboardItems);
return toApolloItem(removedItem);
}
}
}
});
return new ApolloClient({
cache,
uri: "http://localhost:4000",
link: new SchemaLink({
schema
})
});
}
Dashboard.jsx
import { getCubeClient } from './CubeClient';
import { Query } from "react-apollo";
let cubeClient = getCubeClient()
const Dashboard = () => {
const dashboardItem = item => (
<div key={item.id} data-grid={defaultLayout(item)}>
<DashboardItem key={item.id} itemId={item.id} title={item.name}>
<ChartRenderer vizState={item.vizState} />
</DashboardItem>
</div>
);
return(
<Query query={GET_DASHBOARD_ITEMS} client={cubeClient}>
{({ error, loading, data }) => {
if (error) return <div>"Error!"</div>;
if (loading) return (
<div className="alignCenter">
<Spinner color="#be97e8" size={48} sizeUnit="px" />
</div>
);
if (data) {
return (<DashboardView t={translate} dashboardItems={data && data.dashboardItems}>
{data && data.dashboardItems.map(deserializeItem).map(dashboardItem)}
</DashboardView>)
} else {
return <div></div>
}
}}
</Query>
)
};
Explore.jsx
import { useQuery } from "#apollo/react-hooks";
import { withRouter } from "react-router-dom";
import ExploreQueryBuilder from "../components/QueryBuilder/ExploreQueryBuilder";
import { GET_DASHBOARD_ITEM } from "../graphql/queries";
import TitleModal from "../components/TitleModal.js";
import { isQueryPresent } from "#cubejs-client/react";
import PageHeader from "../components/PageHeader.js";
import ExploreTitle from "../components/ExploreTitle.js";
import { PageLayout } from '#gqlapp/look-client-react';
import Helmet from 'react-helmet';
import { translate } from '#gqlapp/i18n-client-react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faAngleRight, faTrashAlt, faPlusCircle } from '#fortawesome/free-solid-svg-icons';
import settings from '#gqlapp/config';
import PropTypes from 'prop-types';
import { getCubeClient } from './CubeClient';
let cubeClient = getCubeClient()
const Explore = withRouter(({ history, location,t }) => {
const [addingToDashboard, setAddingToDashboard] = useState(false);
const params = new URLSearchParams(location.search);
const itemId = params.get("itemId");
const { loading, error, data } = useQuery(GET_DASHBOARD_ITEM, {
client: cubeClient,
variables: {
id: itemId
},
skip: !itemId
});
Titlemodal.jsx
import React from "react";
import { Modal, Input } from "antd";
import { useMutation } from "#apollo/react-hooks";
import { GET_DASHBOARD_ITEMS } from "../graphql/queries";
import {
CREATE_DASHBOARD_ITEM,
UPDATE_DASHBOARD_ITEM
} from "../graphql/mutations";
import { getCubeClient } from '../containers/CubeClient';
let cubeClient = getCubeClient()
const TitleModal = ({
history,
itemId,
titleModalVisible,
setTitleModalVisible,
setAddingToDashboard,
finalVizState,
setTitle,
finalTitle
}) => {
const [addDashboardItem] = useMutation(CREATE_DASHBOARD_ITEM, {
client: cubeClient,
refetchQueries: [
{
query: GET_DASHBOARD_ITEMS
}
]
});
const [updateDashboardItem] = useMutation(UPDATE_DASHBOARD_ITEM, {
client: cubeClient,
refetchQueries: [
{
query: GET_DASHBOARD_ITEMS
}
]
});
queries and mutations.js
import gql from "graphql-tag";
export const GET_DASHBOARD_ITEMS = gql`
query GetDashboardItems {
dashboardItems {
id
layout
vizState
name
}
}
`;
export const GET_DASHBOARD_ITEM = gql`
query GetDashboardItem($id: String!) {
dashboardItem(id: $id) {
id
layout
vizState
name
}
}
`;
export const CREATE_DASHBOARD_ITEM = gql`
mutation CreateDashboardItem($input: DashboardItemInput) {
createDashboardItem(input: $input) {
id
layout
vizState
name
}
}
`;
export const UPDATE_DASHBOARD_ITEM = gql`
mutation UpdateDashboardItem($id: String!, $input: DashboardItemInput) {
updateDashboardItem(id: $id, input: $input) {
id
layout
vizState
name
}
}
`;
export const DELETE_DASHBOARD_ITEM = gql`
mutation DeleteDashboardItem($id: String!) {
deleteDashboardItem(id: $id) {
id
layout
vizState
name
}
}
`;
I'd like to do something like the following so I can get Gatsby Images dynamically:
const image = 'gastby-astronaut.png';
export const imageQuery = graphql`
{ allImageSharp (
filter: {
fluid: {
originalName: {
regex: "/${image}/"
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
`;
However, I can't figure out how to connect this query to an initial query that would get the 'gatsby-astronaut.png', or perform this query from a subcomponent with a . I get this error when I try this:
Error: BabelPluginRemoveGraphQL: String interpolations are not allowed
in graphql fragments. Included fragments should be referenced as
`...MyModule_foo`.
Any suggestions on the proper way to return Gatsby Images dynamically?
Ah, yeah Gatsby extracts GraphQL queries from your pages through static analysis: they load the file as text, parse it, and extract the query, all before the actual file gets executed. This means that your typical tagged-template literal functionality isn't there.
The only way to filter is through context provided when createPage is called from gatsby-node.js. I.e. if you do this:
exports.createPages = ({ graphql, actions }) =>
graphql(`some query here`).then(result => {
actions.createPage({
path: "/output-path/",
component: path.resolve(`./src/templates/your_template.jsx`),
context: { image: result.data.yourImage },
})
})
Then you can do this in your page query:
query SomePage($image: String!) {
allImageSharp (
filter: {
fluid: {
originalName: {
regex: $image
}
}
}
){
edges {
node {
fluid {
originalName
}
}
}
}
}
Here's a solution I came up with... pretty janky, but it works:
import PropTypes from 'prop-types';
import React from 'react';
import Img from 'gatsby-image';
import { useStaticQuery, graphql } from 'gatsby';
const Image = ({ imageYouWant }) => {
const data = useStaticQuery(
graphql`
query allTheImagesQuery{
allImageSharp {
edges {
node {
fluid(maxWidth:1000) {
...GatsbyImageSharpFluid
originalName
}
}
}
}
}`,
);
const TheImageYouWant = data.allImageSharp.edges
.filter(edge => edge.node.fluid.originalName === imageYouWant)
.map(myImage => <Img fluid={myImage.node.fluid} />);
return (
<>
{ TheImageYouWant }
</>
);
};
Image.propTypes = {
imageYouWant: PropTypes.string,
};
Image.defaultProps = {
imageYouWant: '',
};
export default Image;
I've been stuck for hours and I have no idea what's wrong. I'm new to GraphQL and Gatsby.
I have a JSON file that I'm trying to render. Each object has its own url path. I'm able to query the data with GraphQL, set up CreatePages in my gatsby-node.js file and then query again in my template file, but for some reason I'm only getting null in the template file.
I'm pretty sure I'm using gatsby-plugin-transformer-json correctly too. I'm at a loss right now. Any help is much appreciated.
gatsby-node.js
/**
* Implement Gatsby's Node APIs in this file.
*
* See: https://www.gatsbyjs.org/docs/node-apis/
*/
// You can delete this file if you're not using it
const path = require('path');
exports.createPages = ({actions, graphql}) => {
const { createPage } = actions;
const postTemplate = path.resolve(`src/templates/post.js`);
const projectTemplate = path.resolve(`src/templates/project.js`);
return graphql(`{
allProjectsJson {
edges {
node {
id
name
description
path
}
}
}
allMarkdownRemark {
edges {
node {
html
id
frontmatter {
path
title
date
}
}
}
}
}`)
.then(res => {
if(res.errors) {
return Promise.reject(res.errors);
}
// blogs
res.data.allMarkdownRemark.edges.forEach(({node}) => {
createPage({
path: node.frontmatter.path,
component: postTemplate
})
})
// projects
res.data.allProjectsJson.edges.forEach(({node}) => {
createPage({
path: node.path,
component: projectTemplate
})
})
})
}
templates/project.js
import React from 'react';
import { graphql } from 'gatsby'
import Layout from "../components/layout"
// import Helmet from 'react-helmet';
export default function Project({data}) {
const { projectsJson: project } = data;
// this results in null???
console.log(data)
return (
<Layout>
<div>
<h1>Projects!!!</h1>
<p>One single</p>
</div>
</Layout>
);
}
export const projectQuery = graphql`
query ProjectByPath($path: String!) {
projectsJson(path: { eq: $path }) {
name
path
description
}
}
`
I decided to simply list the projects rather than be able to link to each one. With that, I simply had to query in the file rather than pass each object through gatsby-node.js and grab the right one in the template file.
import React from "react"
// import { Link } from "gatsby"
import { graphql } from 'gatsby'
import Layout from "../components/layout"
import SEO from "../components/seo"
const Projects = ({data}) => (
<Layout>
<SEO title="Projects" />
<h1>Personal Projects</h1>
{data.allProjectsJson.edges.map((project, index) => {
console.log(project)
return (
<a
key={index}
style={projectContainerStyles}
href={project.node.url}
target="_blank"
rel="noopener noreferrer"
>
<h2>{project.node.name}</h2>
{project.node.description}
</a>
)
})}
<p>You can see my external portfolio
<a
href="https://anthonyzamarro.github.io/az_portfolio/"
target="_blank"
rel="noopener noreferrer">
here!
</a>
</p>
</Layout>
)
const projectContainerStyles = {
marginBottom: '2rem',
background: 'turquoise',
padding: '8px',
borderRadius: '5px',
boxShadow: '1px 3px 2px rgb(155,155,155)',
'display': 'block',
'color': '#000',
textDecoration: 'none'
}
export const projectQuery = graphql`
query projectQuery {
allProjectsJson {
edges {
node {
id
name
description
url
}
}
}
}`
export default Projects