React button not rerendering the component - javascript

I am making a blog with React and Sanity, and am trying to use buttons to allow the user to sort blog posts by tag (category). Currently, the sorting works fine if I hard code the tag I want to sort the posts by, but I want to be able to use buttons to change the sort tag. Right now the button is able to change text on the screen to the correct category, but It does not update the visible posts (does not actually sort them).
Here is my code for the blog page:
import React from "react";
import { useState, useEffect } from "react"
import Footer from "../Footer";
import Header from "./Header";
import client from "../../client"
import BlockContent from "#sanity/block-content-to-react";
import { Link } from "react-router-dom";
const Blog = () =>
{
const [posts, setPosts] = useState([])
const [sort, setSort] = useState("all")
useEffect(() => {
client.fetch(
//This line sorts posts by the value of 'keyword'
`*[_type == "post" && $keyword in tags] {
title,
slug,
body,
mainImage {
asset -> {
_id,
url
},
alt
},
tags,
publishedAt
}`,{"keyword":sort} //This line sets the value of 'keyword' to the value of 'sort'
)
.then((data) => setPosts(data))
.catch(console.error)
}, [])
//This function prints the tag that was clicked to the console and updates the
//value of 'sort'. It is supposed to rerender the component to display only the
//posts associated with the tag clicked, but it does not.
function sortPosts(e) {
console.log(e)
setSort(e)
}
return (
<div className = "bg-gray-100 dark:bg-zinc-900">
<Header />
<div className = "" >
<p className = "title pt-32">Welcome to My Blog!</p>
//This line correctly displays the tag that is supposed to determine which posts to display
<p className = "text-center font-semibold">Currently showing: {sort} posts</p>
<div className = "flex items-center justify-center mt-16">
<div className = "grid grid-cols-1 xl:grid-cols-3 lg:grid-cols-2 md:grid-cols-1 gap-10 mx-16">
{posts.map((post) => (
<article key={post.slug.current} className = "rounded-xl max-w-sm bg-white dark:bg-zinc-950 shadow-xl dark:shadow-gray-100/10">
<img src={post.mainImage.asset.url} alt={post.title} className = "object-fill object-center rounded-t-xl" />
<div className = "p-6">
<p className = "text-2xl font-semibold mb-3 dark:text-gray-100">{post.title}</p>
{post.tags.map((tags, key) => (
<div className = "inline-block">
//This line displays a button for each tag associated with the current post and calls the sortPosts function
<button key = {key} onClick={() => sortPosts(tags)} className = "px-2 inline-flex mb-2 mr-2 rounded-2xl hover:bg-white bg-gray-100 dark:hover:bg-zinc-950 dark:bg-zinc-800 dark:text-white duration-300 transition-colors cursor-pointer">{tags}</button>
</div>
))}
<div class = "preview">
<BlockContent blocks={post.body} projectId="2hp9gld0" dataset="production" />
</div>
<button className="button-main items-center mt-2 dark:text-gray-100 block">
<Link to = {`/blog/${post.slug.current}`} className = "">Read Full Article</Link>
</button>
</div>
</article>
))}
</div>
</div>
</div>
<div className = "pb-10 bg-gray-100 dark:bg-zinc-900">
<Footer />
</div>
</div>
)
}
export default Blog;
As I said, this code works perfectly fine other than actually rerendering the correct posts when a button is clicked to sort by a specific tag. Any help is appreciated!

If you fetch data and setPosts in useEffect, You should add sort state to useEffect dependency, to update the posts when you change the sort state value by setSort.
like below:
const [posts, setPosts] = useState([])
const [sort, setSort] = useState("all")
useEffect(() => {
client.fetch(
//This line sorts posts by the value of 'keyword'
...
.then((data) => setPosts(data))
.catch(console.error)
}, [sort])

Related

How can I add empty elements into a grid to make it full?

I have an app that gets data of employees and displays their attendance inside the company building.
Each employee is assigned a card and all employees are displayed inside a grid.
I would like to do 2 things:
give a background to the whole grid, so that if there's not enough employees to fill the grid, it would be gray
give each card a white line in between, by either a gap between the cards or a border
How do I do that?
If I apply a background color to the grid, the spacing of the cards doesn't apply (since there are no cards).
If I try to add another card by an after element, only a small, single "card-like" element appears, which is not enough to fill the whole space.
//CardGrid.tsx
import React from "react";
import Card from "./Card";
import { CardGridType } from "../types/componentTypes";
import { UserDataType } from "../types/general";
const CardGrid: React.FC<CardGridType> = ({ data }) => {
return (
<div className="m-4 grid grid-cols-2 overflow-hidden rounded shadow-lg sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-8">
{data.map((d: UserDataType) => (
<Card data={d} key={d.cas} />
))}
</div>
);
};
export default CardGrid;
//Card.tsx
import React from "react";
import { UserDataType } from "../types/general";
const Card: React.FC<{ data: UserDataType }> = ({ data }) => {
const { jmeno, prijmeni, pritomny } = data;
const cardClass =
"py-8 px-8 text-center text-xl text-white " +
(pritomny ? "bg-emerald-600" : "bg-gray-400");
return (
<div className={cardClass}>
<p className="card-text">
{jmeno} {prijmeni}
</p>
</div>
);
};
export default Card;
Please share your advice if you have any idea on how to tackle the problem.
For problem 1, you can add a background color to the CardGrid component itself and set the width and height to 100%.
For problem 2, you can add a border to the Card component with the desired width and color. To add space between the cards, you can add some padding to the CardGrid component.
Here's an updated code for CardGrid.tsx and Card.tsx:
// CardGrid.tsx
import React from "react";
import Card from "./Card";
import { CardGridType } from "../types/componentTypes";
import { UserDataType } from "../types/General";
const CardGrid: React.FC<CardGridType> = ({ data }) => {
return (
<div className="m-4 grid grid-cols-2 overflow-hidden rounded shadow-lg sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-8 bg-gray-200" style={{ width: "100%", height: "100%" }}>
{data.map((d: UserDataType) => (
<Card data={d} key={d.cas} />
))}
</div>
);
};
export default CardGrid;
// Card.tsx
import React from "react";
import { UserDataType } from "../types/General";
const Card: React.FC<{ data: UserDataType }> = ({ data }) => {
const { jmeno, prijmeni, pritomny } = data;
const cardClass =
"py-8 px-8 text-center text-xl text-white " +
(pritomny ? "bg-emerald-600" : "bg-gray-400");
return (
<div className={cardClass} style={{ border: "1px solid white" }}>
<p className="card-text">
{jmeno} {prijmeni}
</p>
</div>
);
};
export default Card;

How to add and remove multiple checkbox values and update nested array in React state hook

I am using React Context to create a multistep form, the form must keep the selection when the user clicks the next or previous step. I am using the below code and it's working fine until I reach the stage to add multiple features using the checkbox, once items are checked, user can go to the previous step to edit and press next to go to the next stage where checked checkboxes must remain checked. I cannot figure out how to push each checkbox value to the features array and remove the item from array when the user uncheck. The important part is to retain the selected despite user go to previous or next step.
Context Provider
import React, { useEffect, useState, createContext } from 'react'
const carSpecs = {
make: '', features: [],model: '',serviceHistory: false, warranty: false, trim: '', bodyType: '', transmission:''
}
export const UsedCarListingContext = createContext({})
export function GlobalUsedCarListingProvider (props) {
const [usedCar, setUsedCar] = useState(carSpecs)
useEffect(() => {}, [usedCar])
return (
<UsedCarListingContext.Provider
value={[usedCar, setUsedCar]}
>
{props.children}
</UsedCarListingContext.Provider>
)
}
Car Details Component
export const UsedCarAdDetails = () => {
const [usedCar, setUsedCar] = useContext(UsedCarListingContext)
const changeHandler = (e) => {
const {name, value} = e.target
setUsedCar({
...usedCar,
[name]: value
})
}
const handleChange = ({target: {name, checked}}) => {
setUsedCar({
...usedCar,
[name]: checked
})
}
return (
<div className="container-1100 bg-white shadow-nav-bar w-full h-52 pt-12">
<NavLink to={'/'} className='landing-nav-logo'>
<img
className='mr-20 mt-4 w-66 ottobay-logo-center'
src={OttobayGray}
alt={'logo'}
/>
</NavLink>
</div>
<div className="container-1050 mg-0auto flex justify-between mt-48">
<div className='container-700 p-20'>
<form>
<div className='ad-listing-input-wrapper mb-16-mobile mb-16 w-full-mobile flex items-center'>
<div className='ad-label-container'>
<label className="listing-input-label font-semibold mr-40"
htmlFor="videoLink">History: </label>
</div>
<div className="ad-input-group-container">
<div className='checkbox-group-container'>
<CheckboxWithImage
onChange={handleChange}
name={'serviceHistory'}
checked={usedCar.serviceHistory}
label={'Service History'}
icon={<GiAutoRepair/>}
checkboxTitleClass={'historyCB'}
/>
<CheckboxWithImage
onChange={handleChange}
name={'warranty'}
checked={usedCar.warranty}
label={'Warranty'}
icon={<AiOutlineFileProtect/>}
checkboxTitleClass={'historyCB'}
/>
</div>
</div>
</div>
<div>
<div className='checkbox-group-wrapper'>
{carFeatures.map(item => (
<div className='feature-item'>
<Checkbox
label={item.name}
onChange={handleFeaturesChange}
checked={usedCar && usedCar.features.some(val => val === item.id)}
value={item.id}
/>
</div>
))}
</div>
</div>
<div className="error-container"></div>
</div>
<div className="car-basic-submission-container">
<div> </div>
<button type='submit' className='search-button bg-button-primary text-white font-semibold rounded-4'> Next Step</button>
</div>
</form>
</div>
)
}
You seem to be calling a non existent function handleFeaturesChange in you feature-item checkbox.
Anyway, something like this should work:
const handleFeaturesChange = ({target: {value, checked}}) => {
setUsedCar({
...usedCar,
features: checked ? [
...usedCar.features,
value, // add the value to previously selected features
] : usedCar.features.filter(val => val !== value) // remove the value
})
}
You could potentially replace the value with name string but then you'd need to update the condition in the checked param of the Checkbox to compare it with the name instead.

I would like to change only clicked header link text color with react

I started to learn to code recently and to create my portfolio. I am working on the header and I would like to change the text color header link tag when it is only clicked once.
When I clicked the link, the text color changed to red and when I clicked the link now but when I click another link, the previous link is still red.
I would like to change the text color back from red to gray when I click another.
Example:- OK - home about work skill contact NO - home about work skill contact
Header.jsx
import React, { useState } from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage } = props;
const [onClickChangeColor, setOnClickChangeColor] = useState(false);
const redText = () => {
setOnClickChangeColor(!onClickChangeColor);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={onClickChangeColor ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
HeaderLayout.jsx
import React from "react";
import Header from "./Header";
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header headerPage={headerPage}>{headerPage}</Header>
))}
<div
className="resume"
class="w-24 h-8 text-[#FF5757] border border-[#FF5757] rounded-md"
>
<a
href="https://drive.google.com/file/d/usp=sharing"
class="block"
>
resume
</a>
</div>
</div>
);
};
export default HeaderLayout;
I am using React and Tailwind. English is my second language and I hope everyone understands what I want to do. Thank you in advance.
change the color handling state to parent (Header Layout)
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
const [selectedHeader, changeSelectedHeader] = useState("");
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header
headerPage={headerPage}
changeSelectedHeader={changeSelectedHeader}
selectedHeader={selectedHeader}
>
{headerPage}
</Header>
))}
then, make the child like this
import React from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage, changeSelectedHeader, selectedHeader } = props;
const redText = () => {
changeSelectedHeader(headerPage);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={headerPage === selectedHeader ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
You can track the selected item index in the HeaderLayout component and then use it to decide the header item color depending on that.
Add another state to maintain the selected header item state in HeaderLayout.
const [selectedIndex, setSelectedIndex] = useState(-1);
Provide the headerIndex, selectedIndex, and setSelectedIndex as props to the Header compoenent.
{headerPages.map((headerPage, headerIndex) => (
<Header
headerPage={headerPage}
setSelectedIndex={setSelectedIndex}
selectedIndex={selectedIndex}
headerIndex={headerIndex}
>
{headerPage}
</Header>
))}
In the Header component get the props and set the header item CSS as below.
const { headerPage, selectedIndex, setSelectedIndex, headerIndex } = props;
...
...
<ScrollLink
className={headerIndex === selectedIndex ? "text-[#FF5757]" : ""}
onClick={() => setSelectedIndex(headerIndex)}
to={headerPage}
smooth={true}
duration={500}
>
Working Demo

pass some value back to parent component in next js

In my nextjs project I'm displaying posts from different tags and each post have many tags. I have a post_by_tags component and I'm using that component in different sections on home page to display posts from different tags. I don't want to show repeating content as some posts have same tags and I have a array to keep post ids that are visible to website. Now I want a way to keep post ids from child component to send back to parent component which updates the array so I can filter out these posts from post object. I find some examples but mostly these are tied with onclick or onchange something like that.
Here is my parent component code:
import Head from 'next/head'
import Image from 'next/image'
import Layout from '../components/Layout';
import Hero from '../components/Hero';
import Developed_country from '../components/Developed_country';
import Posts_by_tags from '../components/Post_by_tags';
import Attorneys from '../components/Attorneys';
import Business_formation from '../components/Business_formation';
import Case from '../components/Case';
export async function getServerSideProps(context) {
// Fetch data from external API
const res = await fetch(`https://dashboard.toppstation.com/api/blogs`);
const data = await res.json();
// Pass data to the page via props
return {
props: { blogs:data}
}
}
export default function Home({blogs}) {
const blog_post_id = [];
const pull_data = (data) => {
console.log(data); // LOGS DATA FROM CHILD)
}
return (
<Layout>
<Hero/>
<Developed_country/>
<Posts_by_tags tag='business' bg='bg_grey' posts={blogs} func={pull_data}/>
<Business_formation/>
<Attorneys/>
<Posts_by_tags tag='png' bg='bg_white' posts={blogs} />
<Posts_by_tags tag='image' bg='bg_grey' posts={blogs} />
<Posts_by_tags tag='png' bg='bg_white' posts={blogs} />
<Case/>
</Layout>
)
}
Child component:
import Blogimg from '../public/img/blog.png';
import btn_arrow from '../public/img/btn_arrow.svg';
import styles from '../styles/Home.module.css';
export default function Posts_by_tags(props){
props.func('My name is xyz');
const bg = props.bg;
let align = ['start','center','end'];
let post_tags = [];
const postIds= [];
const blog_posts = props.posts.filter(bpost=>{
bpost.tags.forEach(tag => {
if(tag.toLowerCase() === props.tag){
return postIds.push(bpost._id);
}
})
});
const posts = props.posts.filter(p => {
if( (postIds.indexOf(p._id) !== -1) && p.visibility==true){
return p;
}
}).slice(0,3);
return(
<>
{posts.length == 0 ? null :(
<section id={styles.postsbytags} className={bg}>
<div className='wrapper'>
<div className="container section posts_by_tags section_ptb">
<div className='row'>
<div className='col-sm-12'>
<h3 className={`${styles.heading3} text-center`}><span className={`${styles.heading3span} ${bg}`}>{props.tag}</span></h3>
</div>
</div>
<div className='row pt_100'>
{posts.map( (post, index) =>(
<div id={`post-${post._id}`} className={`col-md-4 d-flex justify-content-md-${align[index]} justify-content-center`} key={post._id}>
<div className={styles.blog_post}>
<div className={`${styles.blog_image} text-center`}>
<span className={styles.blog_tag}>{props.tag}</span>
<Image className="img-fluid" src={post.image} alt={post.title} width={450} height={400} layout='responsive'/>
</div>
<div className='blog_content'>
<h4 className={styles.blog_title}>{post.title}</h4>
<p className={styles.blog_desc}>{post.description.split(' ').slice(0, 10).join(' ')}...</p>
</div>
</div>
</div>
))}
</div>
<div className='row'>
<div className='col-sm-12'>
<div className='blog_category pt_50'>
<a href="" className={ `btn ${styles.btn_tags} `}>See More {props.tag} <i className={styles.btn_icon}><Image src={btn_arrow} alt="btn-icon"/></i></a>
</div>
</div>
</div>
</div>
</div>
</section>
)}
</>
);
}```

Handle multiple checkbox components in React

I´m having issues with creating my checkbox component(s).
Since I´m mapping over an object´s entries, where the keys are the sectiontitles and the values are arrays, filled with the checkable features.
I´m also using tailwind. I will let the code speek for itself I think :D
Checkboxes
Object structure, which im passing as a prop, down to the checkboxes component:
const features = {
Features: [
'Mintable',
'Burnable',
'Pausable',
'Permit',
'Votes',
'Flash Minting',
'Snapchots'
],
'Access Control': ['Ownable', 'Roles'],
Upgradeability: ['Transparent', 'UUPS']
};
Checkboxes component, which renders the checkbox component multiple times by mapping over the object (Ik the id is missing, but I dont know how to control the ID´s:
import React from 'react';
import { connect, useDispatch } from 'react-redux';
import Checkbox from '../../layout-blueprints/Checkbox';
import { featureCheckboxClick } from './Redux/actions';
const FeaturesComponent = ({ features }) => {
const dispatch = useDispatch();
return (
/* Card layout and heading */
<div className="bg-white p-4 rounded col-span-2 row-span-5">
{Object.entries(features).map(([key, value]) => {
return (
<div key={key}>
<h5 className="text-black m-t-4">{key}</h5>
<div className="border-bottom border-primary pb-2 mb-4 mr-20">
{/* Render feature checkboxes */}
{value.map((feature) => {
return (
<div className="flex justify-between m-2" key={feature}>
<div>
<Checkbox
onChange={() => dispatch(featureCheckboxClick(feature))}
/>
<span className="pl-2 text-lg">{feature}</span>
</div>
</div>
);
})}
</div>
</div>
);
})}
</div>
);
};
export default connect()(FeaturesComponent);
checkbox-component:
import React from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faCheck } from '#fortawesome/free-solid-svg-icons';
const Checkbox = () => {
return (
<label htmlFor="check-box-1" className="cursor-pointer relative">
<input
type="checkbox"
className="check-box-1 appearance-none h-4.5 w-4.5 border-2 border-primary rounded-sm relative translate-y-0.5"
/>
<FontAwesomeIcon
icon={faCheck}
className="-translate-x-4 translate-y-0.5 h-4.5 w-4.5 text-sm absolute text-white text-opacity-0 transition check-1 duration-200"
/>
</label>
);
};
export default Checkbox;
css tailwind directives (should be readable, even if you never worked with tailwind):
#tailwind components;
#tailwind utilities;
.check-box-1:checked {
#apply bg-primary;
}
.check-box-1:checked ~ .check-1 {
#apply text-opacity-100;
}
That´s all the code. Like I said, I´d usually work with a useState array or so, but It´s kinda hard to control it, bc there is so much nesting in the mapping, or am I thinking too complicated?
I´m honestly clueless and am glad for any help I can get, cheers!
I hope this helps
import { NextPage } from "next"
import { useState } from "react"
import CheckboxList from "../../../components/learn22/CheckboxList"
const myFoodList = [
'김치',
'고구마',
'감자',
'피자',
'햄버거',
'라면',
'제육볶음',
'삼계탕',
'닭곰탕',
'추어탕'
]
const MyCheckList: NextPage = () => {
const [checkedFoods, setCheckedFoods] = useState<string[]>([])
const onMyFoodChecked = (checked : string[]) => {
setCheckedFoods(checked)
console.log(checked)
}
return(
<CheckboxList
checkedFoods ={checkedFoods}
onCheck = {onMyFoodChecked}
foods = {myFoodList}
/>
)
}
export default MyCheckList;
import { FunctionComponent } from "react";
interface Props {
checkedFoods ?: string[];
onCheck: (checked : string[]) => void;
foods ?: string[];
}
const CheckBoxList : FunctionComponent<Props> = ({
checkedFoods =[],
onCheck,
foods =[]
}) => {
return (<ol>{foods.map((food, idx)=>
<li key={food + '-' + idx}>
<input type='checkbox' checked={checkedFoods?.includes(food)}onChange={(e)=>{
if(e.target.checked){
onCheck([...checkedFoods!, food])
} else{
onCheck(checkedFoods.filter((_food)=> _food !== food));
}
}}/>
<span>{food}</span>
</li>
)}
</ol> );
}
export default CheckBoxList;
[blog post that shows how to Handle multiple checkbox components in React][1]
[1]: https://codemasterkimc.tistory.com/459

Categories

Resources