I am using Next JS 13 and Sanity 3 GraphQL API to build a simple portfolio site. My problem is to get a single post/project by slug instead of id.
The id url ends up looking like:
http://127.0.0.1:3000/projects/093e9421-3dec-4b22-a106-63dd31b0e685
but I want it to use the slug instead.
This is the getStaticFunction function
export async function getStaticProps({ params }: any) {
const GET_PROJECTS = gql`
query SingleProject($slug: String) {
Project(slug: $slug) {
title
_id
slug {
current
}
bodyRaw
summary
category {
title
}
projectImage {
asset {
url
}
}
}
}
`;
const response = await client.query({
query: GET_PROJECTS,
variables: {
slug: params.slug.current,
},
});
const project = response.data?.Project;
return {
props: {
project,
},
};
}
but this gives me:
ApolloError: Unknown argument "where" on field "Project" of type "RootQuery".
Field "Project" argument "id" of type "ID!" is required, but it was not provided.
I also tried to query using the where like so:
query SingleProject($slug: String!) {
Project(where: { slug: { current: { eq: $slug } } }) {
title
}
}
but I get a Unkown arugment "where" on field "Project" of type "RootQuery"
My understanding is that using serializeIds: 'always' will give me this data, but it does not.
Here's what I'm expecting:
{
id="1"
title="some title"
customerId="2"
}
Instead the output I'm receiving is:
{
id="1"
title="some title"
}
My code looks something like this:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// don't want a root prop
root: false,
// true required to have root:false
embed: true,
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always"
});
export function makeServer() {
let server = newServer({
models: {
invoice: Model.extend({
customer: belongsTo()
}),
customer: Model.extend({
invoices: hasMany()
})
},
factories: {
invoice: Factory.extend({
title(i) {
return `Invoice ${i}`;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer")
});
}
}
}),
customer: Factory.extend({
name() {
let fullName = () =>
`${faker.name.firstName()} ${faker.name.lastName()}`;
return fullName;
}
})
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
invoice: ApplicationSerializer.extend({
include: ["customer"]
})
},
routes() {
this.namespace = "api";
this.get("/auth");
}
});
}
Changing the config to root: true, embed: false, provides the correct output in the invoice models, but adds the root and sideloads the customer, which I don't want.
You've run into some strange behavior with how how serializeIds interacts with embed.
First, it's confusing why you need to set embed: true when you're just trying to disable the root. The reason is because embed defaults to false, so if you remove the root and try to include related resources, Mirage doesn't know where to put them. This is a confusing mix of options and Mirage should really have different "modes" that take this into account.
Second, it seems that when embed is true, Mirage basically ignores the serializeIds option, since it thinks your resources will always be embedded. (The idea here is that a foreign key is used to fetch related resources separately, but when they're embedded they always come over together.) This is also confusing and doesn't need to be the case. I've opened a tracking issue in Mirage to help address these points.
As for you today, the best way to solve this is to leave root to true and embed false, which are both the defaults, so that serializeIds works properly, and then just write your own serialize() function to remove the key for you:
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
You should be able to test this out on both /invoices and /invoices/1.
Check out this REPL example and try making a request to each URL.
Here's the config from the example:
import {
Server,
Serializer,
Model,
belongsTo,
hasMany,
Factory,
} from "miragejs";
import faker from "faker";
const ApplicationSerializer = Serializer.extend({
// will always serialize the ids of all relationships for the model or collection in the response
serializeIds: "always",
serialize(resource, request) {
let json = Serializer.prototype.serialize.apply(this, arguments);
let root = resource.models ? this.keyForCollection(resource.modelName) : this.keyForModel(resource.modelName)
return json[root];
}
});
export default new Server({
models: {
invoice: Model.extend({
customer: belongsTo(),
}),
customer: Model.extend({
invoices: hasMany(),
}),
},
factories: {
invoice: Factory.extend({
title(i) {
return "Invoice " + i;
},
afterCreate(invoice, server) {
if (!invoice.customer) {
invoice.update({
customer: server.create("customer"),
});
}
},
}),
customer: Factory.extend({
name() {
return faker.name.firstName() + " " + faker.name.lastName();
},
}),
},
seeds(server) {
server.createList("invoice", 10);
},
serializers: {
application: ApplicationSerializer,
},
routes() {
this.resource("invoice");
},
});
Hopefully that clears things up + sorry for the confusing APIs!
I can't access a primaryTag variable in my GraphQL page-query.
What I want to achieve is on a blog Post page:
display the post content
display the related posts (based on the first tag)
In my gridsome.server.js
api.createPages(async ({ graphql, createPage }) => {
// Use the Pages API here: https://gridsome.org/docs/pages-api
const { data } = await graphql(`{
allPost {
edges {
node {
id
path
tags {
id
}
}
}
}
}`)
data.allPost.edges.forEach(({ node }) => {
createPage({
path: `${node.path}`,
component: './src/templates/Post.vue',
context: {
id: node.id,
path: node.path,
primaryTag: (node.tags[0] && node.tags[0].id) || '',
}
})
})
})
then in my Post.vue
<page-query>
query Post ($path: String!, $primaryTag: String!) {
post: post (path: $path) {
title
path
content
}
related: allPost(
filter: { tags: { contains: [$primaryTag] }, path: { ne: $path } }
) {
edges {
node {
id
title
path
}
}
}
}
</page-query>
Unfortunately I get the following error: `Variable "$primaryTag" of non-null type "String!" must not be null.
Also, as a side note (and that might be the bug issue) I'm using #gridsome/source-filesystem and #gridsome/transformer-remark to create my Post collection.
If you know how to solve this or have a better approach for getting the related posts, comment below.
Libs:
- gridsome version: 0.6.3
- #gridsome/cli version: 0.1.1`
The basic setup of my little Vue app follows the basic Vue Cli 3 setup:
-main.js (= root)
-- App.vue
--- Navigation.vue (this one calls $root)
In main.js's created() there is this function to load the json for the navigation menu:
[...]
data() {
return {
navMainItems: null
staticTestItems: [
{ "id": 0, "title": "test 1, "url": "/" },
{ "id": 1, "title": "test 2, "url": "/" }
]
}
},
created() {
axios.get('navigation/navAllItems.json')
.then((response) => {
this.navMainItems = response.data.navMainItems;
[...]
}
which works fine.
Now I would like to call that data from the Navigation.vue component using "this.$root":
data(){
return {
navItems: null,
localStaticTestItems: this.$root.staticTestItems
}
},
created() {
// Check if static elements loaded
console.log("sampleNavItems =", this.$root.sampleNavItems)
},
mounted(){
// This fails, presumably because of asynchonicity
// Can this be fixed?
this.navItems = this.$root.navMainItems
}
While the static elements are loaded perfectly fine, the asynchronously loaded navItems do not update.
I am well aware of other approaches like "event bus", "props", or "VueX", but for not I would like to use the dialog with the $root component and learn how to react to asynchonicity - if that is at all possible in this scenario.
I'm not 100% sure, but in your case making navItems in your child component a computed property might work:
computed: {
navItems() { return this.$root.navMainItems }
}
Here's a fiddle with example: http://jsfiddle.net/eywraw8t/268423/
I trying to pass data that is fetched from the server to a popup.I tried doing something like this but its not working.Please help-
{
path:'/merchant/:id',
beforeEnter: function (routeTo, routeFrom, resolve, reject) {
console.log(routeTo.params.id);
Meteor.call('getOne',routeTo.params.id,(error,result) => {
if (result) {
resolve(
{
popup: {
el:document.querySelector('#sample-popup')
}
},
// Custom template context
{
context: {
users: result,
},
}
)
}
});
} ,
},
According to the docs, you use resolve callback wrong !
You can also read this understand how to achieve this