im making a personal site of sorts, where I have a bunch of stylized cards rendering through a map of an array created with GraphQL query.
Those Cards have a tag, and my objective is that when you click said tag, it will filter the original array leaving only the items which include the tag.
Goes Something like this
const Tutoriales = ()=> {
const blog = useStaticQuery(graphql`
query {
allMarkdownRemark (
sort: {fields: frontmatter___date, order: DESC}
)
{
edges {
node {
frontmatter {
title,
date,
type,
abs,
tag,
featuredImage {
relativePath,
absolutePath,
childImageSharp{
fixed(width: 300) {
...GatsbyImageSharpFixed
}
}
}
}
html,
excerpt,
fields {
slug
}
}
}
}
}
`
);
let pijon = blog.allMarkdownRemark.edges;
const [cards, setCards] = useState(pijon);
const [filter, setFilter] = useState("");
return (
<div>
{
cards.map((edge)=> {
<Card className={classes.card}>
<CardHeader
avatar={
<Avatar aria-label="Recipe" className={classes.avatar}>
{edge.node.frontmatter.type}
</Avatar>
}
title={<Link className={center.nodecor} to={url}>{edge.node.frontmatter.title}</Link>}
subheader={<Link className={center.nodecor} to={url}>{edge.node.frontmatter.date} </Link>}
/>
// This is my tag DIV, Im trying to make it work so that when you click
// on the div, it will use the Filter Hook, to filter the card array
<div onClick={ () => {
setFilter(edge.node.frontmatter.tag);
console.log(filter)
let filteredCards = cards.filter((card) => {
return card.node.frontmatter.tag === filter
}
)
setCards(filteredCards);
}
}
className={center.right}>
{edge.node.frontmatter.tag} </div>
</div>
</Card>
)
})
}
</div>
)
}
export default Tutoriales
The expected result would be that the cards hook would be now filtered therefor making all of the cards that do not include the tag selected to dissappear.
But when doing this all I get is a blank array. I know it has something to do with the setState hook being asynchronous, and Ive been reading about the UseEffect, but I cant seem to make it work.
Using console.log I was able to realize, that only the the second time I click the tag (with the onClick value) it works.
Any comment is appreciated.
You are right, this happens beacuse setState
calls are batched. So if you call setFilter
and start using filter
in the same block, you are actually using the previous value of it. That's the reason it works the second time.
setFilter(edge.node.frontmatter.tag);
console.log(filter); <- filter has not actually changed yet
The easiest fix would be to remove const [filter, setFilter] = useState("");
and just use the filter you selected directly:
<div
onClick={() => {
const filter = edge.node.frontmatter.tag;
let filteredCards = cards.filter((card) => {
return card.node.frontmatter.tag === filter
});
setCards(filteredCards);
}}
className={center.right}
>
{edge.node.frontmatter.tag}
</div>
Another solution would be to use the useEffect
hook, and update the cards after the filter
changed:
const [cards, setCards] = useState(pijon);
const [filter, setFilter] = useState("");
useEffect(() => {
if (filter) { // don't do anyhiting when filter is empty on initial render
const filteredCards = cards.filter((card) => {
return card.node.frontmatter.tag === filter
}
setCards(filteredCards);
}
}, [filter, setCards]);
// ...
<div
onClick={() => {setFilter(edge.node.frontmatter.tag);}}
className={center.right}
>
{edge.node.frontmatter.tag}
</div>