I'm trying to make a filter based on a Strapi relation.
My Strapi response looks like this:
I'm using Next Js for the frontend, so I assign props like so:
return {
props: {
projects: data.projects.data,
categories: categoriesData.data.categories.data,
},
}
I map through all of the project and list them all as cards. Then I want to filter them based on a button click. The buttons are the categories names and I map over that array as well. On click of each button I run a function called "handleProjects" and this is where I run into an issue.
I have previously made a filter using SanityCMS and Next Js, however, the code structure of the response in Sanity is much clearer compared to that of Strapi.
This is the code I've used in my previous project with Sanity:
const [filteredProducts, setFilteredProducts] = useState(products)
function handleProducts(e) {
let categoryType = e.target.value
setFilteredProducts(
products.filter((product) => product.category == categoryType)
)
}
I'm using this query where I alias the category as such and it is much simpler:
const productQuery = `*[_type == "product"] {
_id,
slug,
name,
image,
"category": category->name
}`
My function for my Strapi project looks like this:
function handleProjects(e) {
let categoryType = e.target.value
setFilteredProjects(
projects.filter((project) =>
project.attributes.categories.data.map((i) =>
i.attributes.title.includes(categoryType)
)
)
)
}
I'm having an array of objects, but at some point I have to map over an array of objects one level deeper and see if the categoryType matches some of the object values two levels deep.
Can anyone show me what I'm doing wrong here?
Related
I have 2 collection types in my Strapi setup: product and review where a product has many reviews.
I want to add 2 new fields to the response of /products and /products/:id:
averageRaing: number
totalReviews: number
I want to override the default find service to implement this, but I am unable to find the source code for strapi.query("product").find(params, populate) to override it.
If possible, I need this done in a single query rather than making multiple queries.
So far I have:
find(params, populate) {
return strapi.query("product").model.query(db => {
// I want the same query as when I run `strapi.query("product").find(params, populate)`
});
},
But I am unsure of how to handle the params and populate in the exact same way that .find(params, populate) does.
After digging into the source code, I found a solution:
const { convertRestQueryParams, buildQuery } = require("strapi-utils");
function find(params, populate) {
const model = strapi.query("product").model;
const filters = convertRestQueryParams(params);
const query = buildQuery({ model, filters });
return model
.query((qb) => {
const totalReviewsQuery = strapi.connections
.default("reviews")
.count("*")
.where("product", strapi.connections.default.ref("products.id"))
.as("total_reviews");
const averageRatingQuery = strapi.connections
.default("reviews")
.avg("rating")
.where("product", strapi.connections.default.ref("products.id"))
.as("average_rating");
query(qb);
qb.column("*", totalReviewsQuery, averageRatingQuery);
})
.fetchAll({
withRelated: populate,
publicationState: filters.publicationState,
})
.then((results) => results.toJSON());
}
I know adding two arrays together is a rookie situation, but here we are.
Issue/Challenge:
The thing I'm struggling with is that both arrays have the same variable name, data, yet I need to add them.
Let me explain.
I'm doing two queries:
One for graphQL, and
another from an API using the useQuery hook.
However, it seems that both queries require the name data and complains if it's anything different. I've tried the following:
data.push(data),
data.concat(data),
const combinedData = [...data, ...data]
And now I'm knocking on your door for advice.
Here's my code:
const Lesson: FC<PageProps<LessonByIdQuery, { locale: string }>> = ({
//QUERY 1
data,
pageContext: { locale },
location: { pathname },
}) => {
const {
nextLesson,
previousLesson,
contentfulLesson: {
lessonName: title = "",
lessonContent: content,
slug,
createdAt,
updatedAt,
cover,
keywords,
authorCard,
additionalInformation: readMore,
habit: habits,
},
} = data as {
contentfulLesson: ContentfulLesson
nextLesson: ContentfulLesson
previousLesson: ContentfulLesson
}
// QUERY 2
// FetchLessonBookmark IS IMPORTED
const { data, status } = useQuery("lessonTemplateKey", FetchLessonBookmark)
My goal from all of this is to combine the data from the queries and then map out what I need. Thanks in advance!
I have a function to delete a recipe object from an array of recipes objects.
I am actually able to delete a recipe (remove it from the array called recipeList), but when I hit one delete button, it removes ALL of the recipes in the array. I only want to remove the one that I am clicking on.
I am pretty sure it has something to do with this.state.id. It's coming up undefined when I console.log it. It should be referring to the key/id of the recipe being deleted.
Anyone have any ideas what the issue could be?
This is the delete function:
handleDeleteRecipe = recipeId => {
// copy current list of items
const recipeList = [...this.state.recipeList];
//filter out item being deleted
const filteredList = recipeList.filter(recipe => this.state.id !== recipeId);
this.setState({recipeList: filteredList});
console.log('id: ' + this.state.id);
}
In the initial state, id is inside of an object called newRecipe and it is set to empty:
this.state = {
recipeList: [],
newRecipe: {
title: '',
source: '',
servings: '',
id: ''
}
I have an add new recipe function where id is set to a random number between 1 and 1000. No idea if that would have anything to do with it. It is inside of the newRecipe object which then gets added to recipeList array.
handleAddNewRecipe = (title, source, servings, id) => {
const newRecipe = {
title: title,
source: source,
servings: servings,
id: (Math.floor(Math.random()* 1000))
This is where each recipe in the array gets mapped and returned as a Component. It has a key which is set to recipe.id.
{this.state.recipeList.map((recipe) => (
<RecipeCardThumbnail
title={recipe.title}
servings={recipe.servings}
key={recipe.id}
handleDeleteRecipe={this.handleDeleteRecipe}
/>
))}
This is the delete button from the child component:
onClick={() => this.props.handleDeleteRecipe(this.props.id)}>Delete</button>
There's obviously something I am not understanding but I am not sure what. Any help would be much appreciated!
EDIT: I also tried console.logging recipeList when I add my recipe to the array and the id showed up just fine. So I tried recipeList.id and the same issue occurred of all of the recipes getting removed.
EDIT AGAIN: I did an experiment and got rid of everything in the delete function except for the console.log. I tried logging recipeList and everything in the object was there. But when I logged recipeList.id or even recipeList.title they both showed up as undefined. I am still not sure what I am doing wrong though...
this.state.id will return undefined because you don't have id directly under your state.
try this
handleDeleteRecipe = recipeId => {
// copy current list of items
const recipeList = [...this.state.recipeList];
//filter out item being deleted
const filteredList = recipeList.filter(recipe => recipe.id !== recipeId);
this.setState({recipeList: filteredList});
Your filter looks wrong you are never using the recipe object that you are filtering on. I would assume it should be recipeList.filter(recipe => recipe.id !== recipeId)
Also, you do not need to copy the array first... Array.filter will return a new array anyway
confusing title let me explain.
I have an array of categories and array of products.
the array of products is an array of objects, one of the keys is categories, which itself is an array of objects and one of the keys im interested in is title.
the array of categories is an array of objects too.
I want to filter the products array to return me just the products for my chosen category (which is set in state)
I've got this, however it does not work:
filterCategories = () => {
return this.state.products.filter((product => {
return product.categories.map((cat) => {
cat.title == this.state.chosenCategory
})
}))
}
this method seems like it should. to my knowledge, im filtering the products array by mapping over each product and then mapping over that products categories and checking if the category title is equal to the title in state. clearly I've gone wrong somewhere. can anyone see it?
sample data:
products = [
{
id: '123',
title: 'wine',
categories: [
{title: 'drinks'}
]
}
]
categories = [
{
id: '123',
title: 'drinks'
}
]
It looks like you want to test whether any of the category titles match the chosenCategory: so, use the some method. Also, even better, if you're sure there's exactly one matching category, use .find instead of filter. Also make sure to return a value at the end of the internal function (which you aren't doing currently), or use an arrow function with implicit return:
filterCategories = () => (
this.state.products.find((product) => (
product.categories.some(({ title }) => (
title === this.state.chosenCategory
))
))
)
Filter callback function is a predicate, to test each element of the array. you are not returning anything from the inner map function too, you must instead have a filter there like
filterCategories = () => {
return this.state.products.filter((product => {
return product.categories.filter((cat) => {
return cat.title == this.state.chosenCategory
})
}))
}
I have an array saved on my firebase, like this:
matches:[ {match:{id:1,data:...}}]
I want to save just one item on firebase at this array. For example, my match have id:32. I want to find it on the array saved in firebase, and change it.
Im trying to make this. But Im thinking that this is VERY UGLY to make a request to the firebase, copy the array, and save the entire array again.
const ref = `/users/${currentUser.uid}/matches`;
var list = [];
firebase.database().ref(ref).on('value', function (snap) { list = snap.val(); });
if (list.length > 0) {
const indexToUpdate = list.findIndex(k => k.name == match.name)
if (indexToUpdate >= 0) {
list[indexToUpdate] = match;
return dispatch => {
firebase
.database()
.ref(`/users/${currentUser.uid}/matches`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
};
}
}
Any light?
This line of code:
const indexToUpdate = list.findIndex(k => k.name == match.name)
Is a dead giveaway that your data structure is not ideal.
Consider storing the matches under their name, prefixing it with a string to prevent the array coercion that Kenneth mentioned. So:
matches
match1
date: "2018-06-14..."
...
match2
date: "2018-06-16..."
...
With this you can look up the node for a match without needing a query. It also prevents using an array, which is an anti-pattern in Firebase precisely because of the reason you mention.
For more on this, see the classic Firebase blog post on Best Practices: Arrays in Firebase.
Firebase stores arrays as object and converts it back to array when it comes back to the client if the keys are ordered numerically correctly
But basically it should be able to work the same where you make your path up to the object you want to update.
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/${indexToUpdate}`)
.set(match)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
You need to reference the ID of the item you are trying to set by ID in the URL
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/32`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
Also, use snap.key to get the ID you need if index isn't working.