I am deploying a Gatsby site using mdx files. When I use npm run develop, my site works as expected. When I use npm run build, I encounter the following error:
Variable "$slug" of required type "String!" was not provided.
It points to my blog.js file (Url path: /templates/blog/) as the source of the error. After some imports, blog.js appears as follows:
export const query = graphql`
query (
$slug: String!
) {
mdx(
fields: {
slug: {
eq: $slug
}
}
) {
frontmatter {
title
details
date(formatString: "LL")
tags
}
body
}
}`
export default function Blog(props) {
return (...content...
)
}
The relevant parts of my config file is as you would expect.
module.exports = {
siteMetadata: {
title: '...',
author: '...'
},
plugins: [...,
{resolve: 'gatsby-source-filesystem',
options: {
name:'src',
path: `${__dirname}/src/`
}
...
My gatsby-node.js is as follows:
/* pathing via node.js; for path.basename*/
const path = require('path')
/* node function that runs when node is created*/
module.exports.onCreateNode = ({node, actions}) => {
const {createNodeField} = actions
if (node.internal.type === `Mdx`) {
const slug = path.basename(node.fileAbsolutePath, '.mdx')
createNodeField({
node,
name: 'slug',
value: slug
})
}
}
// Creating blog pages
// 1.Get path to template
// 2.Get markdown data
// 3.create new pages
module.exports.createPages = async ({graphql, actions}) => {
const{ createPage } = actions
const blogTemplate = path.resolve('./src/templates/blog.js')
const res = await graphql(`
query {
allMdx {
edges {
node {
fields {
slug
}
}
}
}
}
`)
res.data.allMdx.edges.forEach((edge) => {
createPage({
component: blogTemplate,
path: `/blog/${edge.node.fields.slug}`,
context: {
slug: edge.node.fields.slug
}
})
})
}
I'm quite perplexed, as I can open graphQL and find exactly what I would expect at each stage. What am I doing wrong for the build process to fail yet develop process to work?
It's an odd issue, and it would need a careful debug to know what is happening.
My guess is that in some post, the slug is not properly defined, so it's not matching the String! condition, which means that the slug will be a string (everything ok until here) but non-nullable (because of the exclamation mark, !. Further reference here) so it's breaking your GraphQL query.
Try using it as a nullable field:
export const query = graphql`
query (
$slug: String
) {
mdx(
fields: {
slug: {
eq: $slug
}
}
) {
frontmatter {
title
details
date(formatString: "LL")
tags
}
body
}
}`
Your gatsby-node.js looks good so far.
As I mentioned, the ideal solution would be debugging each post to know if there's any invalid slug (adding a debugger or a console.log and checking its values in the terminal's console).
I’m experiencing the most bizarre Apollo behavior… in my tests, any time I issue a query for which I have a mock, Apollo magically can’t find it.
I have 5 mocks covering queries based on a collectionId and some pagination variables. When I have the component under test look up ID 834940, I get this:
Expected variables: {"page":1,"perPage":20,"collectionId":834940}
Failed to match 4 mocks for this query, which had the following variables:
{"collectionId":374276,"page":1,"perPage":20}
{"collectionId":112805,"page":1,"perPage":20}
{"collectionId":238350,"page":1,"perPage":20}
{"collectionId":-1,"page":1,"perPage":20}
Note that it says “4 mocks”. So it’s not seeing the mock I need, and the query fails. Make sense… until I change the component under test to look up ID 112805, which you can clearly see listed in the 4 mocks Apollo ostensibly knows about.
Expected variables: {"page":1,"perPage":20,"collectionId":112805}
Failed to match 4 mocks for this query, which had the following variables:
{"collectionId":374276,"page":1,"perPage":20}
{"collectionId":834940,"page":1,"perPage":20}
{"collectionId":238350,"page":1,"perPage":20}
{"collectionId":-1,"page":1,"perPage":20}
It can still only see 4 mocks, but the mocks it can see have changed. Now, all of a sudden, 112805 is missing in the mocks it sees. And now it CAN see the mock for 834940, the ID I queried for last time! So basically, whatever I ask for is the query it can’t resolve!!
Has anyone encountered this before?
Context
Collection.tsx:
A functional component with 3 different useQuery calls. Only the third one is failing:
import collectionQuery from './Collection.gql';
export type CollectionProps = {
className?: string;
collection: {
id: number;
name: string;
};
};
export function Collection( props: CollectionProps ) {
/* [useQuery #1] */
/* [useQuery #2] */
// useQuery #3
const {
data, error, loading, refetch,
} = useQuery<CollectionQuery, CollectionQueryVariables>( collectionQuery, {
skip: variables.collectionId === -1,
variables,
} );
return null;
}
queryMocks.ts:
import collectionQuery from './Collection.gql';
import { CollectionQuery_collection } from '../../../graphqlTypes/CollectionQuery';
const page = 1;
const perPage = 20;
export const firstCollection: CollectionQuery_collection = {
id: 374276,
name: 'First Collection',
};
export const secondCollection: CollectionQuery_collection = {
id: 834940,
name: 'Second Collection',
};
export const thirdCollection: CollectionQuery_collection = {
id: 112805,
name: 'Third Collection',
}
export const fourthCollection: CollectionQuery_collection = {
id: 238350,
name: 'Fourth Collection',
};
export const fifthCollection: CollectionQuery_collection = {
id: -1,
name 'Fifth Collection (Error)',
};
export const queryMocks: MockedResponse[] = [
{
request: {
query: collectionQuery,
variables: {
collectionId: firstCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: firstCollection,
},
},
},
{
request: {
query: collectionQuery,
variables: {
collectionId: secondCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: secondCollection,
},
},
},
{
request: {
query: collectionQuery,
variables: {
collectionId: thirdCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: thirdCollection,
},
},
}, {
request: {
query: collectionQuery,
variables: {
collectionId: fourthCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: fourthCollection,
},
},
}, {
request: {
query: collectionQuery,
variables: {
collectionId: fifthCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: fifthCollection,
},
},
},
];
Collection.test.tsx:
import React from 'react';
import { MockedProvider } from '#apollo/client/testing';
import { MemoryRouter } from 'react-router';
import { secondCollection, queryMocks } from './queryMocks';
import { Collection } from './Collection';
it( 'Renders', () => {
render(
<MockedProvider mocks={ queryMocks } addTypename={ false }>
<MemoryRouter>
<Collection collection={ { id: secondCollection.id } } />
</MemoryRouter>
</MockedProvider>,
);
} );
The component is fetching collectionQuery twice, likely due to a state change. This could be from a useState set* call, or it could be from having multiple useQuerys. In the latter case, when one of the 3 queries resolves (with data changing from undefined to defined), it triggers a component re-render, which then calls the other query or queries again.
The reason why this breaks MockedProvider is because each mock can only be used to resolve a single query. So on the first matching API call, the mock is “spent”, reducing the queryMocks length from 5 to 4. Then, when the component re-renders and calls the same query again, Apollo can no longer find a matching mock. So to solve this, you have to either A.) refactor the component to only call the query once, or B.) add two of the same mock to the queryMocks array, like so:
queryMocks.ts:
const secondMock = {
request: {
query: collectionQuery,
variables: {
collectionId: secondCollection.id,
page,
perPage,
},
},
result: {
data: {
collection: secondCollection,
},
},
};
export queryMocks: MockedResponse[] = [
/* [mockOne] */
mockTwo,
mockTwo,
/* [mockThree, ..., mockFive] */
];
I want to create a slug for the url as soon as the user adds an event from the frontend. The slug is based on the name of the event. How to do that in V4 as the old method does not work now?
Slug creation link - old version
By following the article, it seems that you are trying to add lifecycle events to a model. You would need to make the following modifications to the article to make it work for v4.
After the creation of the article model via the admin dashboard, instead of adding the following file:
./api/article/models/Article.js
add:
./src/api/article/content-types/article/lifecycles.js
With the following:
const slugify = require('slugify');
module.exports = {
async beforeCreate(event) {
if (event.params.data.title) {
event.params.data.slug = slugify(event.params.data.title, {lower: true});
}
},
async beforeUpdate(event) {
if (event.params.data.title) {
event.params.data.slug = slugify(event.params.data.title, {lower: true});
}
},
};
Also the api endpoint changed in v4 so you would need to use:
GET /api/articles?filters[slug]=my-article-slug
This seem to work for me
Settings > Roles > Public > Slugify (checkbox findSlug)
config/plugins.js
module.exports = ({ env }) => ({
slugify: {
enabled: true,
config: {
contentTypes: {
page: {
field: "slug",
references: "name",
},
post: {
field: "slug",
references: "name",
},
category: {
field: "slug",
references: "name",
},
},
},
},
});
graphql
const POSTS = gql`
query GetPosts {
posts {
... on PostEntityResponseCollection {
data {
__typename
id
attributes {
__typename
name
slug
content
featuredImage {
data {
__typename
id
attributes {
url
alternativeText
caption
}
}
}
createdAt
}
}
}
}
}
`;
const POST = gql`
query GetPost($slug: String!) {
findSlug(modelName: "post", slug: $slug, publicationState: "live") {
... on PostEntityResponse {
data {
__typename
id
attributes {
createdAt
name
slug
content
seo {
__typename
id
title
description
blockSearchIndexing
}
categories {
__typename
data {
__typename
id
attributes {
__typename
name
slug
}
}
}
}
}
}
}
}
`;
I am trying to create a page displaying my pinned repos. I am using gatsby and have installed the gatsby-source-github-api
{
resolve: 'gatsby-source-github-api',
options: {
token: 'xxxxxxxxx',
},
},
And I have the query populating the data I want in the GraphQL plaryground.
query {
user(login: "mrpbennett") {
pinnedItems(first: 6, types: [REPOSITORY]) {
edges {
node {
... on Repository {
name
description
url
primaryLanguage {
name
color
}
}
}
}
}
}
}
However I am struggling to get that data to populate into a new component I keep getting 7:13 error Cannot query field "user" on type "Query" graphql/template-strings
This is the component
I am not really to sure how to populate the data i need.
import React from 'react'
import { graphql, useStaticQuery } from 'gatsby'
const PinnedRepos = () => {
const data = useStaticQuery(graphql`
query {
user(login: "mrpbennett") {
pinnedItems(first: 6, types: [REPOSITORY]) {
edges {
node {
... on Repository {
name
description
url
primaryLanguage {
name
color
}
}
}
}
}
}
}
`)
return (
<div>
<p>{data.node.repository.name}</p>
</div>
)
}
export default PinnedRepos
any advice would be greatly appreciated.
The syntax of your query is the problem here, you need to write the query like
const data = useStaticQuery(graphql`
query user(login: "mrpbennett") {
pinnedItems(first: 6, types: [REPOSITORY]) {
edges {
node {
... on Repository {
name
description
url
primaryLanguage {
name
color
}
}
}
}
}
}
`)
For some reason, if I generate a root query which takes in parameters before injecting the child component, like so:
import Relay from 'react-relay';
export default {
production: (Component) => Relay.QL`
query {
getProduction(id: $productionId) {
${Component.getFragment('production')}
}
}
`
};
Relay originally generates this query:
query MyProductionDetailsQuery($id_0:ID!,$where_1:ProductionRoleWhereArgs!) {
getProduction(id:$id_0) {
id,
...F0
}
}
fragment F0 on Production {
id,
...
_roles4oPiwv:roles(first:10,where:$where_1) {
edges {
node {
id,
...
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
}
}
variables:
{id_0: "UHJvZHVjdGlvbjoxNg==", where_1: {archived: {eq: true}}}
However, If the Component's relay container has variables of its own, running this.props.relay.setVariables({...variables}) completely changes the request query generated by relay into something like this:
query My_production_details_page_ProductionRelayQL($id_0:ID!,$where_1:ProductionRoleWhereArgs!) {
node(id:$id_0) {
...F0
}
}
fragment F0 on Production {
id,
_roles6J5gK:roles(first:10,where:$where_1) {
edges {
node {
id,
...
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
}
}
variables:
{id_0: "UHJvZHVjdGlvbjoxNg==", where_1: {archived: {eq: false}}}
However, setVariables works fine if I have a root query with no parameters:
import Relay from 'react-relay';
export default {
viewer: (Component, variables) => Relay.QL`
query {
viewer {
${Component.getFragment('viewer', { ...variables })}
}
}
`
};
Here's the generated query:
query ViewerQuery($where_0:ProductionWhereArgs!) {
viewer {
...F0
}
}
fragment F0 on Viewer {
user {
_productions2IPZAw:productions(first:10,where:$where_0) {
edges {
node {
id,
...
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
},
id
}
}
variables:
{where_0: {expDate: {gt: "2016-11-04T16:29:11.677Z"}, archived: {eq: false}}}
After setVariables in the working setup:
query ViewerQuery($where_0:ProductionWhereArgs!) {
viewer {
...F0
}
}
fragment F0 on Viewer {
user {
_productions1CyNvL:productions(first:10,where:$where_0) {
edges {
node {
id,
...
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
},
id
}
}
variables:
{where_0: {expDate: {lt: "2016-11-04T16:34:12.537Z"}, archived: {eq: false}}}
versions:
"react-relay": "^0.9.3",
"react-router-relay": "^0.13.5"
I'm not sure if I'm doing something wrong with the configuration, or if it's just a bug on Relay's end.
Does anyone know what might be causing this issue?
When you run setVariables it leads to refetching only necessary data.
Relay looks which part of query could be affected by changing variable and requests from GraphQL server needed fragment.
It is possible because of Node interface(i.e. fetching object by opaque Relay id). See more in documentation.
I think, in your case you should implement Node Interface for Production type on GraphQL server.