I've two tabs, auto and manual.
When i switch the tab to manual, it should only validate the tracking_number. If switch to "auto", it should validate the recipient_name.
const validationschema= yup.object().shape({
tab: yup.string().oneOf([
"auto",
"manual",
])
.required(),
tracking_number: yup.string().when("tab", {
is: "manual",
then: yup.string().required()
}),
recipient_name: yup.string().when("tab", {
is: "auto",
then: yup.string().required()
})
})
But this code doesn't work, it wont show any error. I did initialize the initialValue.
<Formik
initialValues={{
tab: "auto",
tracking_number: "",
recipient_name: contact_name || "",
}}
validationSchema={validationschema}
onSubmit={handleOnChange}
>
Any ideas?
End up using if else.
const validationSchema = (tab) => {
let shape;
if (tab === "auto") {
shape = {
recipient_name: yup.string().required(),
}
} else if (tab === "manual") {
shape = {
tracking_number: yup.string().required(),
}
}
const schema = yup.object().shape(shape);
return schema;
}
Related
This is my mongoose schema. but when i send PATCH req from client. the options (array) validation not work. but others field's validation work.
I search in online but dont get the problem. How can I slove it. Thank you.
const optionsValidator = function (options) {
console.log("Validating options...");
const MinLength = 2;
const MaxLength = 6;
const MCQCode = 0;
// checking options are required or not for the question;
if (this.questionType !== MCQCode) {
throw new Error("Options are required only for MCQ");
}
if (options.length < MinLength) {
throw new Error(`At least ${MinLength} options are required`);
}
if (options.length > MaxLength) {
throw new Error(`Maximum ${MaxLength} options can be created`);
}
// make options lower case before checking uniqueness of the options
const lowerOptions = options.map((option) => option.toLowerCase());
if (lowerOptions.length !== new Set(lowerOptions).size) {
throw new Error("Options are not unique");
}
// options are validated
return true;
};
const questionSchema = new Schema({
quesId: { type: String, required: [true, "Id_required"] },
title: { type: String, required: [true, "title_required"] },
questionType: {
type: Number,
default: 0,
},
options: {
type: [String],
default: undefined,
required: function () {
return this.questionType === 0 && !this.options;
},
validate: {
validator: optionsValidator,
message: (props) => props.reason.message,
},
},
});
const updatedData = { ...questionData };
let optionsData;
if (updatedData.options) {
data = await Question.findById(id);
optionsData = {
$push: { options: { $each: updatedData.options } },
};
delete updatedData.options;
}
exports.patchQuestion = async (id, questionData) => {
return (result = await Question.findOneAndUpdate(
{ _id: id },
{ ...optionsData, $set: updatedData },
{ new: true, runValidators: true }
));
}
The is the PATCH request send form client.
{ "options": ["A","A"] }
I have a few questions regarding the schema customization.
data/categories.json
[
{
"name": "Foo Bar",
"slug": "foo-bar"
},
{
"name": "Fuz Baz",
"slug": "fuz-baz"
}
]
gatsby-config.js
...
mapping: {
"MarkdownRemark.frontmatter.author": `AuthorsJson.alias`,
"MarkdownRemark.frontmatter.category": `CategoriesJson.name`
}
gatsby-node.js
exports.createSchemaCustomization = ({ actions, schema }) => {
const { createTypes } = actions;
const typeDefs = [
`type AuthorsJson implements Node #dontInfer {
alias: String,
name: String
slug: String,
bio: String,
profile: String
posts: [MarkdownRemark] #link(by: "frontmatter.author.alias", from: "alias")
}`,
`type CategoriesJson implements Node #dontInfer {
name: String,
slug: String,
posts: [MarkdownRemark] #link(by: "frontmatter.category.name", from: "name")
}`,
];
createTypes(typeDefs);
}
The bottom-line question is, how do I accomplish frontmatter validation?
From the question above, how do I force each post to supply a category AND the category must exist in the categories.json (e.g. they can't make up their own category)?
If front matter is an array, how do I ensure that no more than 3 elements exist in the array?
Thanks!
Option 1:
This is what I have in gatsby-node.js:
var categories = [];
var aliases = [];
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if(node.internal.type === `CategoriesJson`) {
categories.push(node.name);
}
if(node.internal.type === `AuthorsJson`) {
aliases.push(node.alias);
}
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
if (node.frontmatter.author === null || node.frontmatter.author === undefined) {
throw "Page is missing alias: " + node.fileAbsolutePath;
}
if (aliases.indexOf(node.frontmatter.author) == -1) {
throw "Page has invalid alias: " + node.fileAbsolutePath;
}
if (node.frontmatter.category === null || node.frontmatter.category === undefined) {
throw "Page is missing category: " + node.fileAbsolutePath;
}
if (categories.indexOf(node.frontmatter.category) == -1) {
throw "Page has invalid category ('" + node.frontmatter.category +"'): " + node.fileAbsolutePath;
}
if (node.frontmatter.tags.length > 3) {
throw "Page has more than 3 tags: " + node.fileAbsolutePath;
}
}
}
Is there a better way?
You can simplify all your conditions by:
let categories = [];
let aliases = [];
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if(node.internal.type === `CategoriesJson`) {
categories.push(node.name);
}
if(node.internal.type === `AuthorsJson`) {
aliases.push(node.alias);
}
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
if (!node.frontmatter.author || !aliases.includes(node.frontmatter.author)) {
throw "Page is missing alias: " + node.fileAbsolutePath;
}
if (!node.frontmatter.category || !categories.includes(node.frontmatter.category)) {
throw "Page is missing category: " + node.fileAbsolutePath;
}
if (node.frontmatter.tags.length > 3) {
throw "Page has more than 3 tags: " + node.fileAbsolutePath;
}
}
}
I've also added let instead of var, which is kind of deprecated nowadays. Check the difference at https://www.javascripttutorial.net/es6/difference-between-var-and-let/ and replaced the indexOf == -1 for the more semantic includes.
For the rest, the approach looks quite solid to me, it's a good way.
If front matter is an array, how do I ensure that no more than 3
elements exist in the array?
By default (and as it should be) frontmatter will be always an object.
I am new to composition API with vue3. I have created that computed property and I would like to have that computed variable in a different file, I'm not sure if I should create a new component or I could achieve it from a js file.
Here is the component working (I did it with setup()):
export default {
name: "Recipes",
setup() {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoritesRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
...
});
...
}
return {
...toRefs(state),
favoriteRecipes
}
// end of setup
}
You can split it into two files
state.js
export const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
export const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
})
and recipes.vue
import { state, favoriteRecipes } from "state.js";
export default {
name: "Recipes",
setup() {
return {
...toRefs(state),
favoriteRecipes,
};
},
};
But this will make the state persistent, so if you have multiple components, they will all have the same favoriteRecipes and state values.
If you want them to be unique for each component...
state.js
export const withState = () => {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter((item) => {
return item.favorite;
});
}
return tempFavs;
});
return { state, favoriteRecipes };
};
and recipes.vue
import { withState } from "state.js";
export default {
name: "Recipes",
setup() {
const {state, favoriteRecipes} = withState()
return {
...toRefs(state),
favoriteRecipes,
};
},
};
Hi Guys I'm trying to filter post with data json format field?
"categoryList": ["cat", "cat1"]
For anyone still looking for a solution, this is what I have done for a json type field called tags of a collection type called Articles.
I have two articles in the database with one article having the following values set:
title: "lorem ipsum 1",
tags: [
"test",
"rest"
]
The other article has the following values set:
title: "lorem ipsum 2",
tags: [
"test",
"graphql"
]
My graphql query looks like this:
query {
articlesByTag(limit: 2, where: {tags_include: ["test", "rest"]}, start: 0, sort: "title:asc") {
title,
tags
}
}
While my rest query looks like this:
http://localhost:1337/articlesByTag?limit=2&tags_include[]=test&tags_include[]=rest
This is my articles.js service file:
const { convertRestQueryParams, buildQuery } = require('strapi-utils');
const _ = require('lodash');
const { convertToParams, convertToQuery } = require('../../../node_modules/strapi-plugin-graphql/services/utils');
module.exports = {
async findByTag(ctx) {
let tags_include;
if (ctx.where && ctx.where.tags_include && ctx.where.tags_include.length > 0) {
tags_include = ctx.where.tags_include;
delete ctx.where.tags_include;
} else if (ctx.query && ctx.query.tags_include && ctx.query.tags_include.length > 0) {
tags_include = ctx.query.tags_include;
delete ctx.query.tags_include;
}
if (!Array.isArray(tags_include)) {
tags_include = [tags_include];
}
let filters = null;
if (ctx.query) {
filters = convertRestQueryParams({
...convertToParams(ctx.query)
});
} else {
filters = convertRestQueryParams({
...convertToParams(_.pick(ctx, ['limit', 'start', 'sort'])),
...convertToQuery(ctx.where),
});
}
const entities = await strapi.query('articles').model.query(qb => {
buildQuery({ model: strapi.query('articles').model, filters: filters })(qb);
if (tags_include.length > 0) {
tags_include.forEach((tag) => {
if (tag && tag.length > 0) {
const likeStr = `%"${tag}"%`;
qb.andWhere('tags', 'like', likeStr);
}
});
}
}).fetchAll();
return entities;
},
};
This is the entry needed in routes.js
{
"method": "GET",
"path": "/articlesByTag",
"handler": "articles.findByTag",
"config": {
"policies": []
}
}
This is the controller articles.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async findByTag(ctx) {
const entities = await strapi.services.articles.findByTag(ctx);
return entities.map(entity => sanitizeEntity(entity, { model: strapi.models.articles }));
},
};
And finally this is the schema.graphql.js
module.exports = {
query: `
articlesByTag(sort: String, limit: Int, start: Int, where: JSON): [Articles]
`,
resolver: {
Query: {
articlesByTag: {
description: 'Return articles filtered by tag',
resolverOf: 'application::articles.articles.findByTag',
resolver: async (obj, options, ctx) => {
return await strapi.api.articles.controllers.articles.findByTag(options);
},
},
},
},
};
There is not currently a way to filter the JSON fields yet as of beta.17.8 (latest)
Probably something like that?
strapi.query('cool_model').find({ categoryList: { $all: [ "cat" , "cat1" ] } })
I have this useSiren hook that should update its state with the incoming json argument but it doesnt.
On the first call the json is an empty object, because the fetch effect has not been run yet.
On the second call its also an empty object (triggered by loading getting set to true in App)
And on the third call its filled with valid data. However, the valid data is not applied. The state keeps its initial value.
I guess somehow setSiren must be called to update it, since initial state can only be set once. But how would I do that? Who should call `setSiren?
import { h, render } from 'https://unpkg.com/preact#latest?module';
import { useEffect, useState, useCallback } from 'https://unpkg.com/preact#latest/hooks/dist/hooks.module.js?module';
import htm from "https://unpkg.com/htm#latest/dist/htm.module.js?module";
const html = htm.bind(h);
function useFetch({
method = "GET",
autoFetch = true,
href,
body
}) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState()
const [response, setResponse] = useState()
const [isCancelled, cancel] = useState()
const [json, setJson] = useState({})
const sendRequest = async payload => {
try {
setLoading(true)
setError(undefined)
const response = await fetch(href.replace("http://", "https://"), {
method
})
const json = await response.json()
if (!isCancelled) {
setJson(json)
setResponse(response)
}
return json
} catch (err) {
if (!isCancelled) {
setError(err)
}
throw err
} finally {
setLoading(false)
}
}
if (autoFetch) {
useEffect(() => {
sendRequest(body)
return () => cancel(true)
}, [])
}
return [{
loading,
response,
error,
json
},
sendRequest
]
}
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
function Action(props) {
const [{ loading, error, json }, sendRequest] = useFetch({ autoFetch: false, href: props.href, method: props.method })
const requestAndUpdate = () => {
sendRequest().then(props.onRefresh)
}
return (
html`
<button disabled=${loading} onClick=${requestAndUpdate}>
${props.title}
</button>
`
)
}
function App() {
const [{ loading, json }, sendRequest] = useFetch({ href: "https://restlr.io/toggle/0" })
const [{ state, actions }, setSiren] = useSiren(json)
return (
html`<div>
<div>State: ${loading ? "Loading..." : (state.properties && state.properties.value)}</div>
${actions.map(action => html`<${Action} href=${action.href} title=${action.title || action.name} method=${action.method} onRefresh=${setSiren}/>`)}
<button disabled=${loading} onClick=${sendRequest}>
REFRESH
</button>
</div>
`
);
}
render(html`<${App}/>`, document.body)
Maybe what you want to do is to update the siren state when the json param changes? You can use a useEffect to automatically update it.
function useSiren(json) {
const [{ entities = [], actions = [], links, title }, setSiren] = useState(json)
useEffect(() => { // here
setSiren(json)
}, [json])
const state = (entities.find(entity => entity.class === "state")) || {}
return [
{
title,
state,
actions
},
setSiren
]
}
The pattern mentioned by #awmleer is packaged in use-selector:
import { useSelectorValue } from 'use-selector';
const { entities=[], actions=[], title} = json;
const siren = useSelectorValue(() => ({
state: entities.find(entity => entity.class === 'state') || {},
actions,
title
}), [json]);
Disclosure I'm author and maintainer of use-selector