How to create a dynamic slug in Strapi v4? - reactjs

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

Related

Variable "$slug" of required type "String!" was not provided only when building yet not when developing

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).

React Apollo Client - query results mixing up in cache

I use apollo client to fetch book graphs and use relay style pagination. Both of the following NEW_BOOKS query and ALL_BOOKS query works fine independently.
Currently, I am using NEW_BOOKS in the home page and ALL_BOOKS in a popup in the home page.
When the Homepage is opened NEW_BOOKS gets loaded fine.
When the popup is opened and ALL_BOOKS is fetched, newBooks become undefined or the result of ALL_BOOKS query.
Why is this happening?
const { loading, data: newBooks, fetchMore, networkStatus } = useQuery(NEW_BOOKS, {
variables: {
first: PAGE_SIZE,
after: endCursor
},
notifyOnNetworkStatusChange: true
});
const NEW_BOOKS = gql`query GetNewBooks($first:Int!, $after:String){
books(
first: $first, after: $after,
filters: [
{
path: "isNew",
value: true
}
]
) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
name
author {
id
name
}
}
}
}
}`;
-All books query filterable by name
const { loading, data: filteredBooks, fetchMore, networkStatus } = useQuery(ALL_BOOKS, {
variables: {
first: PAGE_SIZE,
after: endCursor,
name: nameFilter
},
notifyOnNetworkStatusChange: true
});
const ALL_BOOKS = gql`query GetAllBooks($first:Int!, $after:String, $name:String){
books(
first: $first, after: $after,
filters: [
{
path: "name",
value: $name,
type: "contains"
}
]
) {
totalCount
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
name
copiesSold
author {
id
name
}
}
}
}
}`;
The cache being used looks like this,
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: relayStylePagination(),
},
}
},
});
We have to pass keyArgs explictly when relayStylePagination or similar pagination is used.
A keyArgs: ["type"] field policy configuration means type is the only argument the cache should consider (in addition to the field name and the identity of the enclosing object) when accessing values for this field. A keyArgs: false configuration disables the whole system of differentiating field values by arguments, so the field's value will be identified only by the field's name (within some StoreObject), without any serialized arguments appended to it.
KeyArgs documentation here.
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: relayStylePagination(["name"]),
},
}
},
});

How can I specify optional query filters in Prisma?

I am building an application with React, Apollo and Prsima that allows users to filter cars by model, brand, price... I know (for example) how to filter cars by brand:
const GET_CARS = gql`
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId } }
}) {
id
model {
name
brand {
name
}
horses
}
year
km
price
...
}
`;
And in the component:
const CarList = (props) => {
const { data, loading, error } = useQuery(GET_CARS, {
variables: {
brandId: "exampleBrandId"
}
})
...
}
The problem is, that some parameters are optional: maybe the user does not care about the brand, or the model, or the price... So then all cars should appear: If no brand is selected, cars of all brands should appear; If no price is selected, cars of all prices should appear...
How can I do that? Something like:
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId || all_brand_ids } }
}) {
...
}
}
I have investigated a and found a possible solution, but the custom input that the post refers to is not generated in my prisma.
getCars(parent, args, {prisma}, info){
const queryArgs ={}
if(args.brandId){
queryArgs ={
where: {
model: {
id: args.brandId
}
}
}
}
if(args.price){
queryArgs={
...queryArgs,
where: {
...queryArgs.where
//What every you want to add
}
}
}
return prisma.query.cars(queryArgs, info)
}
You can check on the Backend your args. And create a flexible obj to query for...
The answer from #Toiz is feasible. However, what if the number of parameters increases so that multiple if-statements are needed?
I suggest using undefined.
As mentioned in prisma official docs here, you can use undefined to optioanally exclude a field from query.
For example,
where: {
model : {
brand: {
id: $brandId != null ? $brandId : undefined
}
}
}
const GET_CARS = gql`
query FilterCars($brandId: ID){
cars(where: {
model : { brand: { id: $brandId } }
}) {
id
model {
name
brand {
name
}
horses
}
year
km
price
}
`;
const CarList = (props) => {
const { data, loading, error } = useQuery(GET_CARS, {
variables: {
brandId: "exampleBrandId"
}
})
}
brandId: "exampleBrandId" - this should be dynamic, and one of the options could be something like "All Brands", which would then query all brands.

Handling Graphql Mutation update, cache read and writeQuery, if the query is dynamic?

Doing nightlife app on freecodecamp https://learn.freecodecamp.org/coding-interview-prep/take-home-projects/build-a-nightlife-coordination-app/
I am trying to implement 'Go' button, similarly 'Like' button on Youtube or Instagram. Users click the button the number(counting how many users go) goes up meaning users will go there and click again, it revokes, the number decreases, users will not go there.
It seems like working well except the issue, I have to refresh the page and then, the number has increased or decreased and throws the error like below so:
Invariant Violation: Can't find field getBars({}) on object {
"getBars({\"location\":\"vancouver\"})": [
{
"type": "id",
"generated": false,
"id": "Bar:uNgTjA9ADe_6LWby20Af8g",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:CwL5jwXhImT_7K5IB7mOvA",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:mdt1tLbkZcOS2CsEbVF9Xg",
"typename": "Bar"
},
.
.
.
I am assuming handling update function will fix this issue but unlike the example from Apollo documentation:
// GET_TODOS is not dynamic query
// nothing to pass as variables to fetch TODO list
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
My query is dynamic:
// I have to pass location variable, otherwise it won't fetch anything.
const GET_BARS_QUERY = gql`
query getBars($location: String!) {
getBars(location: $location) {
id
name
url
rating
price
image_url
goings {
username
}
goingCount
}
}
`;
I believe I might need to handle to provide location using readQuery and writeQury but not too sure what I should do.
Here's my code:
const GoButton = ({ user, bar }) => {
const { token } = user;
const { id, goings, goingCount } = bar;
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data });
}
});
return (
<Button onClick={userGoes}>
Go {goingCount}
</Button>
);
};
const GO_MUTATION = gql`
mutation go($yelp_id: String!) {
go(yelp_id: $yelp_id) {
id
goings {
id
username
}
goingCount
}
}
`;
export default GoButton;
Full code here https://github.com/footlessbird/Nightlife-Coordination-App
when you read/write the getBars query, you need to pass the location as a variable
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY,
variables: {
location: 'New York'
}
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data,
variables: {
location: 'New York'
}
});
}
});

Relay mutation on plain array is not working

I am having a hard time figuring out how to do mutations on plain array via relay.
I am trying to add a new tag to a post.
It does not get updated on client-side after being successfully added on the server-side.
I have to manually reload to see the new tag.
I have tried both REQUIRED_CHILDREN and this.props.relay.forceFetch(), but to no avail.
Also, tried FIELDS_CHANGE for post.
GraphQL Schema:
Post {
id: ID!
text: String!
tags: [Tag!]!
}
Tag {
id: ID!
name: String!
}
AddTagToPostMutation:
static fragments = {
post: () => Relay.QL`
fragment on Post {
id
tags
}
`,
}
getMutation() {
return Relay.QL`mutation { addTagToPost }`;
}
getVariables() {
return {
name: this.props.tag.name,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddTagToPostMutationPayload {
tag {
id
name
}
post {
id
tags
}
}
`;
}
getConfigs() {
return [{
type: 'REQUIRED_CHILDREN',
children: [Relay.QL`
fragment on AddTagToPostMutationPayload {
tag {
id
name
}
post {
id
tags
}
}
`],
}];
}
getOptimisticResponse() {
return {
tag: {
name: this.props.tag.name,
},
post: {
id: this.props.post.id,
},
};
}
As freiksenet already pointed out, FIELDS_CHANGE should be used in getConfigs() function. I took your schema, implemented the GraphQL types, server-side and client-side mutation to add tag to a post. The client-side gets updated successfully. I'm going to just elaborate the solution in my answer.
First, check your server-side mutation. My implementation uses graphql and graphql-relay libraries and looks like below. Notice that the output of the server-side mutation is a post to which a tag has been added. This post is the one whose ID was provided as input.
const AddTagToPostMutation = mutationWithClientMutationId({
name: 'AddTagToPost',
inputFields: {
postId: { type: new GraphQLNonNull(GraphQLID) },
name: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
post: {
type: PostType,
resolve: ({id}) => getPost(id),
},
},
mutateAndGetPayload: ({postId, name}) => {
const id = fromGlobalId(postId).id;
addTagToPost(id, name);
return {id};
},
});
Using graphiql, you can test your mutation:
mutation {
addTagToPost(input:{
postId: "UG9zdDpwb3N0Mg=="
name:"a new tag name"
clientMutationId:"123244"}) {
post {
id
text
tags {
id
name
}
}
}
}
I added a field posts for all posts to the root query. Using graphiql, I first checked the post IDs and used one above.
Using react-relay, the client-side mutation code looks like below. It is passed a prop post whose ID is used as input variable in getVariables() function. In the getConfigs() function, we specify that post field has to be updated. The association between the payload field post and the passed prop post is established using FIELDS_CHANGE mutation type.
export default class AddTagToPostMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation{addTagToPost}`;
}
getVariables() {
return {
postId: this.props.post.id,
name: this.props.name,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddTagToPostPayload {
post {
id,
tags {
id,
name,
}
}
}
`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
post: this.props.post.id,
},
}];
}
static fragments = {
post: () => Relay.QL`
fragment on Post {
id,
}
`,
};
}
The client-side mutation is invoked like this:
Relay.Store.commitUpdate(new AddTagToPostMutation({
post: postToModify,
name: tagName,
}));
I think you should just use FIELDS_CHANGE in such situations.
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {post: this.props.post.id},
}];
}
getOptimisticResponse() {
return {
post: {
id: this.props.post.id,
tags: [...this.props.post.tags, this.props.tag],
},
};
}

Resources