Skip to content

Commit a922943

Browse files
authored
fix(gatsby): Multiple root query error (#36525)
1 parent b029564 commit a922943

File tree

6 files changed

+58
-33
lines changed

6 files changed

+58
-33
lines changed

docs/docs/how-to/routing/mdx.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ For further reading, check out the [createPages](/docs/reference/config-files/ga
319319

320320
### Make a layout template for your posts
321321

322-
You can create a file called `posts.jsx` in `src/templates` - this component will be rendered as the template for all posts. Now, create a component that accepts your compiled MDX content via `children` and uses GraphQL `data` to show the title. **Please note:** The query must **not** have a name. This way Gatsby can create a unique one internally and the `__contentFilePath` in the next step will work correctly.
322+
You can create a file called `posts.jsx` in `src/templates` - this component will be rendered as the template for all posts. Now, create a component that accepts your compiled MDX content via `children` and uses GraphQL `data` to show the title:
323323

324324
```jsx:title=src/templates/posts.jsx
325325
import React from "react"

e2e-tests/mdx/src/pages/fs-api/{Mdx.fields__slug}.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function FSAPIComponent(props) {
1414
}
1515

1616
export const query = graphql`
17-
query($id: String) {
17+
query SomeQueryName($id: String) {
1818
mdx(id: { eq: $id }) {
1919
internal {
2020
contentFilePath

packages/gatsby-plugin-mdx/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,6 @@ However, we'd recommend placing such files into the `src/pages` directory and `g
665665

666666
1. Instead of querying for the `body` on the MDX node, the page template now receives the transformed MDX as `children` property.
667667
1. You no longer need to use `<MDXRenderer>` as you can use `{children}` directly.
668-
1. If you have given your query a name (e.g. `query PostTemplate`) you have to remove the name. Gatsby automatically creates a name internally, but if a name is provided you'll see a "Duplicate query" warning.
669668

670669
```diff
671670
import React from "react"
@@ -686,9 +685,8 @@ import { graphql } from "gatsby"
686685
)
687686
}
688687

689-
export const pageQuery = graphql`
690-
- query PostTemplate($id: String!) {
691-
+ query ($id: String!) {
688+
export const pageQuery = graphql`
689+
query PostTemplate($id: String!) {
692690
mdx(id: { eq: $id }) {
693691
frontmatter {
694692
title

packages/gatsby/src/query/__tests__/__snapshots__/query-compiler.js.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Map {
2020
id
2121
}",
2222
"path": "mockFile",
23+
"templatePath": "mockFile",
2324
"text": "fragment PostsJsonFragment on PostsJson {
2425
id
2526
}
@@ -51,6 +52,7 @@ Object {
5152
}
5253
}",
5354
"path": "mockFile",
55+
"templatePath": "mockFile",
5456
"text": "fragment PostsJsonFragment on PostsJson {
5557
id
5658
}
@@ -85,6 +87,7 @@ Object {
8587
id
8688
}",
8789
"path": "mockFile",
90+
"templatePath": "mockFile",
8891
"text": "fragment PostsJsonFragment on PostsJson {
8992
id
9093
}
@@ -115,6 +118,7 @@ Object {
115118
}
116119
}",
117120
"path": "mockFile",
121+
"templatePath": "mockFile",
118122
"text": "query mockFileQuery {
119123
allPostsJson {
120124
nodes {
@@ -142,6 +146,7 @@ Object {
142146
}
143147
}",
144148
"path": "mockFile",
149+
"templatePath": "mockFile",
145150
"text": "query mockFileQuery {
146151
allPostsJson {
147152
nodes {
@@ -209,6 +214,7 @@ Map {
209214
}
210215
}",
211216
"path": "mockFile",
217+
"templatePath": "mockFile",
212218
"text": "query mockFileQuery {
213219
allPostsJson {
214220
nodes {
@@ -236,6 +242,7 @@ Object {
236242
}
237243
}",
238244
"path": "mockFile",
245+
"templatePath": "mockFile",
239246
"text": "fragment PostsJsonFragment on PostsJson {
240247
id
241248
...AnotherPostsJsonFragment
@@ -279,6 +286,7 @@ Object {
279286
id
280287
}",
281288
"path": "mockFile",
289+
"templatePath": "mockFile",
282290
"text": "fragment PostsJsonFragment on PostsJson {
283291
id
284292
}

packages/gatsby/src/query/__tests__/query-compiler.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ describe(`actual compiling`, () => {
289289
"name": "page",
290290
"originalText": "query page { allPostsJson { nodes { id } } }",
291291
"path": "mockFile",
292+
"templatePath": "mockFile",
292293
"text": "query page {
293294
allPostsJson {
294295
nodes {
@@ -461,6 +462,7 @@ describe(`actual compiling`, () => {
461462
}
462463
}",
463464
"path": "mockFile1",
465+
"templatePath": "mockFile1",
464466
"text": "fragment Foo on Directory {
465467
id
466468
}
@@ -497,6 +499,7 @@ describe(`actual compiling`, () => {
497499
}
498500
}",
499501
"path": "mockFile2",
502+
"templatePath": "mockFile2",
500503
"text": "fragment Bar on Directory {
501504
parent {
502505
...Foo
@@ -1126,6 +1129,7 @@ describe(`actual compiling`, () => {
11261129
}
11271130
}",
11281131
"path": "mockFile",
1132+
"templatePath": "mockFile",
11291133
"text": "query mockFileQuery {
11301134
allPostsJson {
11311135
nodes {

packages/gatsby/src/query/query-compiler.js

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const { store } = require(`../redux`)
3333
import { actions } from "../redux/actions"
3434

3535
import { websocketManager } from "../utils/websocket-manager"
36+
import { getPathToLayoutComponent } from "gatsby-core-utils"
3637
const { default: FileParser } = require(`./file-parser`)
3738
const {
3839
graphqlError,
@@ -236,7 +237,12 @@ const extractOperations = (schema, parsedQueries, addError, parentSpan) => {
236237
const name = def.name.value
237238
let printedAst = null
238239
if (def.kind === Kind.OPERATION_DEFINITION) {
239-
operations.push(def)
240+
operations.push({
241+
def,
242+
filePath,
243+
templatePath: getPathToLayoutComponent(filePath),
244+
hash,
245+
})
240246
} else if (def.kind === Kind.FRAGMENT_DEFINITION) {
241247
// Check if we already registered a fragment with this name
242248
printedAst = print(def)
@@ -277,6 +283,7 @@ const extractOperations = (schema, parsedQueries, addError, parentSpan) => {
277283
isConfigQuery,
278284
isFragment: def.kind === Kind.FRAGMENT_DEFINITION,
279285
hash: hash,
286+
templatePath: getPathToLayoutComponent(filePath),
280287
})
281288
})
282289
}
@@ -303,31 +310,9 @@ const processDefinitions = ({
303310
.map(([name, _]) => name)
304311

305312
for (const operation of operations) {
306-
const name = operation.name.value
313+
const { filePath, templatePath, def } = operation
314+
const name = def.name.value
307315
const originalDefinition = definitionsByName.get(name)
308-
const filePath = definitionsByName.get(name).filePath
309-
310-
// Check for duplicate page/static queries in the same component.
311-
// (config query is not a duplicate of page/static query in the component)
312-
// TODO: make sure there is at most one query type per component (e.g. one config + one page)
313-
if (processedQueries.has(filePath) && !originalDefinition.isConfigQuery) {
314-
const otherQuery = processedQueries.get(filePath)
315-
316-
addError(
317-
multipleRootQueriesError(
318-
filePath,
319-
originalDefinition.def,
320-
otherQuery && definitionsByName.get(otherQuery.name).def
321-
)
322-
)
323-
324-
store.dispatch(
325-
actions.queryExtractionGraphQLError({
326-
componentPath: filePath,
327-
})
328-
)
329-
continue
330-
}
331316

332317
const { usedFragments, missingFragments } =
333318
determineUsedFragmentsForDefinition(
@@ -359,10 +344,11 @@ const processDefinitions = ({
359344
kind: Kind.DOCUMENT,
360345
definitions: Array.from(usedFragments.values())
361346
.map(name => definitionsByName.get(name).def)
362-
.concat([operation]),
347+
.concat([operation.def]),
363348
}
364349

365350
const errors = validate(schema, document)
351+
366352
if (errors && errors.length) {
367353
for (const error of errors) {
368354
const { formattedMessage, message } = graphqlError(
@@ -396,14 +382,43 @@ const processDefinitions = ({
396382
continue
397383
}
398384

385+
const printedDocument = print(document)
386+
// Check for duplicate page/static queries in the same component.
387+
// (config query is not a duplicate of page/static query in the component)
388+
// TODO: make sure there is at most one query type per component (e.g. one config + one page)
389+
if (processedQueries.has(filePath) && !originalDefinition.isConfigQuery) {
390+
const otherQuery = processedQueries.get(filePath)
391+
392+
if (
393+
templatePath !== otherQuery.templatePath ||
394+
printedDocument !== otherQuery.text
395+
) {
396+
addError(
397+
multipleRootQueriesError(
398+
filePath,
399+
originalDefinition.def,
400+
otherQuery && definitionsByName.get(otherQuery.name).def
401+
)
402+
)
403+
404+
store.dispatch(
405+
actions.queryExtractionGraphQLError({
406+
componentPath: filePath,
407+
})
408+
)
409+
continue
410+
}
411+
}
412+
399413
const query = {
400414
name,
401-
text: print(document),
415+
text: printedDocument,
402416
originalText: originalDefinition.text,
403417
path: filePath,
404418
isHook: originalDefinition.isHook,
405419
isStaticQuery: originalDefinition.isStaticQuery,
406420
isConfigQuery: originalDefinition.isConfigQuery,
421+
templatePath,
407422
// ensure hash should be a string and not a number
408423
hash: String(originalDefinition.hash),
409424
}

0 commit comments

Comments
 (0)