React Component rendered from Value: State not updating - javascript

I am building an Account Setup Page where Users have to select a Role and get different Setup Steps. I tried to create two different Arrays containing the Step Components for each Role to ease adding new components into the process.
Unfortunately, the States for both creative and company are not updating. For the role state, it works. E.g. when I try to log the value of creative.firstName in a useEffect hook, it only gives me one letter. The state in the Creative First Name component is not updating at all.
This is my Setup Page where I update the steps array when the role value changes:
import { NextPage } from "next";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import { Company, Creative } from "../../../customTypes";
import CompanyName from "../../components/welcome/company/CompanyName";
import FirstName from "../../components/welcome/creative/FirstName";
import UserRole from "../../components/welcome/UserRole";
const Welcome: NextPage = () => {
const [role, setRole] = useState<"creative" | "company" | null>(null);
const [creative, setCreative] = useState<Creative>({
firstName: "",
lastName: "",
artistName: "",
handle: "",
});
const [company, setCompany] = useState<Company>({
name: "",
handle: "",
});
type Step = {
caption?: string;
component: ReactNode;
validate: () => boolean;
};
const STEPS: Step[] = [
{
caption: "Select your role",
component: <UserRole role={role} setRole={setRole} />,
validate: () => {
if (role === null) return false;
return true;
},
},
];
const CREATIVE_STEPS: Step[] = [
{
component: <FirstName creative={creative} setCreative={setCreative} />,
validate: () => {
if (creative.firstName === "") return false;
return true;
},
},
];
const COMPANY_STEPS: Step[] = [
{
component: <CompanyName company={company} setCompany={setCompany} />,
validate: () => {
if (company.name === "") return false;
return true;
},
},
];
const [step, setStep] = useState<number>(0);
const [steps, setSteps] = useState<Step[]>(STEPS);
useEffect(() => {
if (role === "creative") {
setSteps([...STEPS, ...CREATIVE_STEPS]);
}
if (role === "company") {
setSteps([...STEPS, ...COMPANY_STEPS]);
}
}, [role]);
return (
<div className="mx-auto flex h-screen max-w-3xl flex-col justify-center px-4 py-12">
<h1 className="mb-12 text-2xl font-bold">{steps[step]?.caption}</h1>
<div className="flex flex-col">{steps[step]?.component}</div>
<div className="mt-4 flex items-center justify-end gap-2">
{step > 0 && (
<button
onClick={() => step > 0 && setStep(step - 1)}
className="rounded-md border py-2 px-6 text-sm text-text hover:border-dark hover:text-dark dark:border-light/50 dark:text-light/50 dark:hover:border-light dark:hover:text-light"
>
Back
</button>
)}
<button
onClick={() =>
step < steps.length - 1 &&
steps &&
steps[step]?.validate() &&
setStep(step + 1)
}
className={`rounded-md border border-primary bg-primary py-2 px-6 text-sm text-light ${
steps && steps[step]?.validate() ? "opacity-100" : "opacity-50"
}`}
>
Next
</button>
</div>
<div className="fixed top-0 left-0 right-0 h-2 bg-gray-200">
<div
className={`absolute left-0 top-0 bottom-0 z-10 h-full bg-primary duration-300 ease-[cubic-bezier(0.48,0.21,0.7,1.16)] ${
step === steps.length ? "rounded-none" : "rounded-r-md"
}`}
style={{
width: step > 0 ? (step / steps.length) * 100 + "%" : "15%",
}}
></div>
</div>
</div>
);
};
export default Welcome;
This is my component the change the first name:
import { useEffect } from "react";
import { Creative } from "../../../../customTypes";
interface Props {
creative: Creative;
setCreative: React.Dispatch<React.SetStateAction<Creative>>;
}
const FirstName: React.FC<Props> = ({ creative, setCreative }) => {
return (
<div className="flex flex-col">
<label className="font-semibold">First Name</label>
<p className="mt-2 text-xs text-text dark:text-light/60">
Your First Name will not be published.
</p>
<input
type="text"
className="mt-2 rounded-md border bg-transparent py-2 px-4 text-sm"
onChange={(e) => {
setCreative((prev: Creative) => ({
...prev,
firstName: e.target.value,
}));
}}
value={creative.firstName}
/>
</div>
);
};
export default FirstName;
This is my Creative type:
export type Creative = {
firstName: string;
lastName: string;
artistName: string;
handle: string;
};
What am I doing wrong?
Thanks in advance.

Related

How do I use my functions to other page in typescirpt?

This page is "detail.tsx".Mainly is to use "LikeButton" to another pages.
import React, { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { GoVerified } from 'react-icons/go';
import Image from 'next/image';
import Link from 'next/link';
import { MdOutlineCancel } from 'react-icons/md';
import { BsFillPlayFill } from 'react-icons/bs';
import { HiVolumeUp, HiVolumeOff } from 'react-icons/hi';
import Comments from '../../components/Comments';
import LikeButton from '../../components/LikeButton';
import useAuthStore from '../../store/authStore';
import { Video } from '../../types';
import axios from 'axios';
interface IProps {
postDetails: Video;
}
const Detail = ({ postDetails }: IProps) => {
const [post, setPost] = useState(postDetails);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [isVideoMuted, setIsVideoMuted] = useState<boolean>(false);
const [isPostingComment, setIsPostingComment] = useState<boolean>(false);
const [comment, setComment] = useState<string>('');
const videoRef = useRef<HTMLVideoElement>(null);
const router = useRouter();
const { userProfile }: any = useAuthStore();
useEffect(() => {
if (post && videoRef?.current) {
videoRef.current.muted = isVideoMuted;
}
}, [post, isVideoMuted]);
const handleLike = async (like: boolean) => {
if (userProfile) {
const res = await axios.put(`https://.../api/like`, {
userId: userProfile._id,
postId: post._id,
like
});
setPost({ ...post, likes: res.data.likes });
}
};
const addComment = async (e: { preventDefault: () => void }) => {
e.preventDefault();
if (userProfile) {
if (comment) {
setIsPostingComment(true);
const res = await axios.put(`https://.../api/post/${post._id}`, {
userId: userProfile._id,
comment,
});
setPost({ ...post, comments: res.data.comments });
setComment('');
setIsPostingComment(false);
}
}
};
return (
<>
{post && (
<div className='flex w-full absolute left-0 top-0 bg-white flex-wrap lg:flex-nowrap'>
<div className='relative flex-2 w-[1000px] lg:w-9/12 flex justify-center items-center bg-blurred-img bg-no-repeat bg-cover bg-center'>
<div className='opacity-90 absolute top-6 left-2 lg:left-6 flex gap-6 z-50'>
<p className='cursor-pointer ' onClick={() => router.back()}>
<MdOutlineCancel className='text-white text-[35px] hover:opacity-90' />
</p>
</div>
<div className='relative'>
<div className='lg:h-[100vh] h-[60vh]'>
<video
ref={videoRef}
controls
loop
src={post?.video?.asset.url}
className=' h-full cursor-pointer'
></video>
</div>
<div className='absolute top-[45%] left-[40%] cursor-pointer'>
</div>
</div>
<div className='absolute bottom-5 lg:bottom-10 right-5 lg:right-10 cursor-pointer'>
</div>
</div>
<div className='relative w-[1000px] md:w-[900px] lg:w-[700px]'>
<div className='lg:mt-10 mt-10'>
<Link href={`/profile/${post.postedBy._id}`}>
<div className='flex gap-4 mb-4 bg-white w-full pl-10 cursor-pointer'>
<Image
width={60}
height={60}
alt='user-profile'
className='rounded-full'
src={post.postedBy.image}
/>
<div>
<div className='text-xl font-bold captilize tracking-wider flex gap-2 items-center justify-center'>
{post.postedBy.userName.replace(/\s+/g, '')}{' '}
<GoVerified className='text-blue-400 text-xl' />
</div>
<p className='text-md lowercase'> {post.postedBy.userName}</p>
</div>
</div>
</Link>
<div className='px-10'>
<p className=' text-lg text-black-100'>{post.caption}</p>
</div>
<div className='mt-5 px-10'>
{userProfile && <LikeButton
likes={post.likes}
flex='flex'
handleLike={() => handleLike(true)}
handleDislike={() => handleLike(false)}
/>}
</div>
<Comments
comment={comment}
setComment={setComment}
addComment={addComment}
comments={post.comments}
isPostingComment={isPostingComment}
/>
</div>
</div>
</div>
)}
</>
);
};
export {LikeButton};
export const getServerSideProps = async ({
params: { id },
}: {
params: { id: string };
}) => {
const res = await axios.get(`https://.../api/post/${id}`);
return {
props: { postDetails: res.data },
};
};
export default Detail;
// export const LikeButton = 'handlelike';
This is my main "LikeButton.tsx" page which got referred from "detail.tsx" page
import React, { useEffect, useState } from 'react';
import { MdFavorite } from 'react-icons/md';
import { NextPage } from 'next';
import useAuthStore from '../store/authStore';
// import { AiOutlineStar, GiFallingStar } from 'react-icons/gi';
import { BsStar } from 'react-icons/bs';
import { AiFillStar } from 'react-icons/ai';
interface IProps {
likes: any;
flex: string;
handleLike: () => void;
handleDislike: () => void;
}
const LikeButton: NextPage<IProps> = ({ likes, flex, handleLike, handleDislike }) => {
const [alreadyLiked, setAlreadyLiked] = useState(false);
const { userProfile }: any = useAuthStore();
let filterLikes = likes?.filter((item: any) => item._ref === userProfile?._id);
useEffect(() => {
if (filterLikes?.length > 0) {
setAlreadyLiked(true);
} else {
setAlreadyLiked(false);
}
}, [filterLikes, likes]);
return (
<div className={`${flex} gap-6`}>
<div className='mt-4 flex flex-col justify-center items-center cursor-pointer'>
{alreadyLiked ? (
<div className='bg-primary rounded-full p-2 md:p-4 text-[#006ee6] ' onClick={handleDislike} >
<AiFillStar className='text-lg md:text-2xl' />
</div>
) : (
<div className='bg-primary rounded-full p-2 md:p-4 ' onClick={handleLike} >
<AiFillStar className='text-lg md:text-2xl' />
</div>
)}
<p className='text-md font-semibold '>{likes?.length || 0}</p>
</div>
</div>
);
};
export default LikeButton;
I would like to use my "LikeButton" function from "detail.tsx" to another pages (not worked with import and export). Is there any advice to use only "LikeButton" functionally in another pages?

Double filter only filters when one is active

I'm trying to create a filter that filters an array of objects by genre and by text.
The desired outcome should be if no filter is passed then everything is displayed,
when filtering by text the relevant results are displayed (considering which genre is active) and results by genre are displayed when genre is active considering which text is passed (if any text had been passed)
what happens is that when i try to search by text nothing happens unless i set the filter by genre.
my filter is
async function query(filterBy) {
let melodies = _loadMelodiesFromStorage();
try {
if (!filterBy) return melodies;
if (filterBy.genre === 'none') {
return melodies;
}
const filterRegex = new RegExp(filterBy.text, 'i');
let filteredMelodies = melodies.filter((melody) => {
return filterRegex.test(melody.name);
});
console.log('filteredMelodies:', filteredMelodies);
filteredMelodies = filteredMelodies.filter((melody) => {
return melody.genre === filterBy.genre;
});
return filteredMelodies;
} catch (err) {
console.log('cant get melodies from local storage', err);
throw err;
}
}
this is the filter component :
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { loadMelodies } from '../cmps/store/melody.action.js';
import { melodyService } from '../service/melody.service.js';
const Filters = () => {
const dispatch = useDispatch();
const genres = melodyService.getGenres();
const [filter, setFilter] = useState({
text: null,
genre: 'none',
});
useEffect(() => {
dispatch(loadMelodies(filter));
}, [filter]);
return (
<div className='w-full flex pb-5'>
<input
className='block appearance-none border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline'
type='text'
placeholder='Search...'
value={filter.text}
onChange={(e) => setFilter({ ...filter, text: e.target.value })}
/>
<select
className='block appearance-none border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline'
value={filter.genre}
onChange={(e) => setFilter({ ...filter, genre: e.target.value })}>
{genres.map((genre) => (
<option value={genre.toLowerCase()}>{genre}</option>
))}
</select>
</div>
);
};
export default Filters;
I would try to simplify it a bit, maybe something like this:
const query = ({ genre, text } = {}) => {
let list = melodies //get them somewhat
if (genre) {
list = list.filter(m => m.genre === genre)
}
if (text) {
list = list.filter(m => m.text.includes(text))
}
return list
}
const melodies = [
{ genre: 'g1', text: 'this is my text' },
{ genre: 'g2', text: 'another text' },
{ genre: 'g2', text: 'description' }
]
const query = ({ genre, text } = {}) => {
let list = melodies
if (genre) {
list = list.filter(m => m.genre === genre)
}
if (text) {
list = list.filter(m => m.text.includes(text))
}
return list
}
console.log(query({ text: 'text' }))
console.log(query({ genre: 'g2' }))
console.log(query({ genre: 'g2', text: 'description' }))
console.log(query())
Does this condition not mean that if your genre filter is set to 'none' then your query function will return all results not having the chance of executing the text filter?
I suggest you separate your query function into several smaller ones.
if (filterBy.genre === 'none') {
return melodies;
}
I've made it work by adding
if (!filterBy || (filterBy.text === '' && filterBy.genre === 'None')) {
return melodies;
}
might not be the best solution but it works.

How should you handle complex filters in react

So i am working on a project where i have a large amount of content to display. Because i dont want the page to be overflown with content i divided it into pages of 6 posts. I also want to implement a filtering system with a searchbar, difficultyslider and categories.
I got everything working but i am stuck with making the pagination work. This is because i need the length of the final filtered array so i know when to jump back to page 1. The problem is i do all the filtering inside the return statement of my component and need a way to do it above. That way i can just map over the filtered array instead of needing to use 3 filter functions. The component will also be way easier to read and split up because at the moment its pretty much chaos.
image of the final product.
these are the arrays which are imported from other files
export const filterKeys = [
"all",
"airtricks",
"spins",
"flips",
"grabs",
"obstacles",
"dock",
];
export const difficulties = [
{
name: "Beginner",
color: "var(--light-blue)",
bgColor: "var(--light-blue-bg)",
},
{
name: "Novice",
color: "var(--lime-green)",
bgColor: "var(--lime-green-bg)",
},
{
name: "Intermediate",
color: "var(--dark-yellow)",
bgColor: "var(--dark-yellow-bg)",
},
{
name: "Advanced",
color: "var(--light-orange)",
bgColor: "var(--light-orange-bg)",
},
{
name: "Expert",
color: "var(--dark-orange)",
bgColor: "var(--dark-orange-bg)",
},
{
name: "Pro",
color: "var(--dark-red)",
bgColor: "var(--dark-red-bg)",
},
{
name: "All difficulties",
color: "var(--p-col)",
},
];
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import {
HiOutlineChevronDoubleRight,
HiOutlineChevronDoubleLeft,
} from "react-icons/hi";
import { urlFor, client } from "../../client";
import { Wrapper } from "../../wrapper";
import { difficulties, filterKeys } from "../../constants";
import "./Tricklist.scss";
const Tricklist = () => {
// tricklist states
const [currentDifficulty, setCurrentDifficulty] = useState(7);
const [activeFilter, setActiveFilter] = useState("All");
const [filteredTricks, setFilteredTricks] = useState([]);
const [tricks, setTricks] = useState([]);
// searchbar states
const [formData, setFormData] = useState({ name: "" });
const { name } = formData;
// pagination states
const [tricksPerPage] = useState(6);
const [currentPage, setCurrentPage] = useState(1);
const indexOfLastTrick = currentPage * tricksPerPage;
const indexOfFirstTrick = indexOfLastTrick - tricksPerPage;
useEffect(() => {
const query = '*[_type == "tricks"]';
client.fetch(query).then((data) => {
setTricks(data);
setFilteredTricks(data);
});
}, []);
const handleDifficulty = (difficulty) => {
setCurrentDifficulty(difficulty + 1);
};
const handleChangeInput = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleFilter = (item) => {
setActiveFilter(item);
if (item === "all") {
setFilteredTricks(tricks);
} else {
setFilteredTricks(tricks.filter((trick) => trick.tags.includes(item)));
}
};
return (
<div className="app__tricklist">
<div className="app__tricklist-filters">
<h3 className="heading-text">
Trick <span className="highlighted">List</span>
</h3>
<p className="sub-heading">Filters</p>
<div className="filters-panel">
<div className="top-row">
<input
type="text"
placeholder="Trick Name"
value={name}
onChange={handleChangeInput}
name="name"
className="searchbar"
/>
<div className="difficulty-slider">
<h4>
Difficulty:
<span
style={{
color: `${difficulties[currentDifficulty - 1].color}`,
}}
>
{difficulties[currentDifficulty - 1].name}
</span>
</h4>
<ul className="dots">
{difficulties.map((difficulty, index) => (
<div
key={difficulty.name}
className="dot"
onClick={() => handleDifficulty(index)}
style={{
backgroundColor: `${
index < currentDifficulty
? difficulties[currentDifficulty - 1].color
: "var(--white)"
}`,
}}
/>
))}
</ul>
</div>
</div>
<div className="bottom-row">
<ul className="category-filters">
{filterKeys.map((filterKey, index) => (
<motion.button
whileInView={{ opacity: [0, 1], x: [200, 0] }}
transition={{ duration: 0.5, delay: index * 0.1 }}
key={filterKey}
onClick={() => handleFilter(filterKey)}
className={`${
activeFilter === filterKey ? "active-filter" : ""
}`}
>
{filterKey}
</motion.button>
))}
</ul>
</div>
</div>
</div>
{tricks && (
<ul className="app__tricklist-list">
{filteredTricks
.filter((trick) => {
if (
trick.difficulty === currentDifficulty ||
currentDifficulty === 7
) {
return trick;
}
})
.filter((trick) => {
if (formData.name === "") {
return trick;
} else if (
trick.name.toLowerCase().includes(formData.name.toLowerCase())
) {
return trick;
}
})
.slice(indexOfFirstTrick, indexOfLastTrick)
.map((trick) => (
<motion.li
key={trick.name}
className="app__tricklist-card"
whileInView={{ opacity: [0, 1], y: [300, 0] }}
transition={{
duration: 0.3,
ease: "easeOut",
}}
whileHover={{ scale: 1.1 }}
>
<img src={urlFor(trick.image)} alt={trick.name} />
<div className="card-body">
<div className="card-header">
<h4>{trick.name}</h4>
<p
className="difficulty-rating uppercase"
style={{
backgroundColor:
difficulties[trick.difficulty - 1].bgColor,
color: difficulties[trick.difficulty - 1].color,
}}
>
{difficulties[trick.difficulty - 1].name}
</p>
</div>
<p className="p-text">{trick.description}</p>
</div>
</motion.li>
))}
</ul>
)}
<div className="app__tricklist-pagination">
<button
className="prev-btn"
onClick={() => setCurrentPage(currentPage - 1)}
>
<HiOutlineChevronDoubleLeft />
prev
</button>
<button
className="next-btn"
onClick={() => setCurrentPage(currentPage + 1)}
>
next
<HiOutlineChevronDoubleRight />
</button>
</div>
</div>
);
};
export default Wrapper(Tricklist, "Trick-List");

Cart Array in useState won't update with onClick Event

I just began learning React and learning how to use Context Api. I am trying to update my cart when user clicks the add to cart button from the product page but for some reason it won't update the state.
Here is my code:
context api:
import React, { createContext, useEffect, useState } from 'react';
import { detailProduct, storeProducts } from './data';
const ProductContext = createContext();
const ProviderContext = ({ children }) => {
const [products, setProducts] = useState({
product: [],
detailsProduct: detailProduct,
cart: [],
modalOpen: false,
modalProduct: detailProduct,
});
const { product, detailsProduct, cart, modalOpen, modalProduct } = products;
const newProducts = () => {
let tempProducts = [];
storeProducts.forEach((item) => {
const singleItem = { ...item };
tempProducts = [...tempProducts, singleItem];
});
setProducts({
...products,
product: tempProducts,
});
};
useEffect(() => {
newProducts();
}, []);
const getItem = (id) => {
const singleProduct = product.find((item) => item.id === id);
return singleProduct;
};
const handleDetail = (id) => {
const newProduct = getItem(id);
setProducts({ ...products, detailsProduct: newProduct });
};
const addToCart = (id) => {
let tempProducts = [...product];
const index = tempProducts.indexOf(getItem(id));
const cartProduct = tempProducts[index];
cartProduct.inCart = true;
cartProduct.count = 1;
const price = cartProduct.price;
cartProduct.total = price;
setProducts({
...products,
product: tempProducts,
cart: [...cart, cartProduct],
});
console.log(products);
};
const openModal = (id) => {
const product = getItem(id);
setProducts({ ...products, modalProduct: product, modalOpen: true });
console.log(products);
};
const closeModal = () => {
setProducts({ ...products, modalOpen: false });
};
return (
<ProductContext.Provider
value={{
...products,
handleDetail: handleDetail,
addToCart: addToCart,
openModal: openModal,
closeModal: closeModal,
}}
>
{children}
</ProductContext.Provider>
);
};
const ConsumerContext = ProductContext.Consumer;
export { ProviderContext, ConsumerContext };
My product page:
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { ConsumerContext } from '../Context';
import PropTypes from 'prop-types';
const Product = ({ product }) => {
const { id, title, img, price, inCart } = product;
return (
<ProductWrapper className='col-9 mx-auto col-md-6 col-lg-3 my-3'>
<div className='card'>
<ConsumerContext>
{(value) => (
<div
className='img-container p-5'
onClick={() => value.handleDetail(id)}
>
<Link to='/details'>
<img src={img} alt='product' className='card-img-top' />
</Link>
<button
className='cart-btn'
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
value.openModal(id);
}}
>
{inCart ? (
<p className='text-capitalize mb-0' disabled>
in cart
</p>
) : (
<i className='fas fa-cart-plus'></i>
)}
</button>
</div>
)}
</ConsumerContext>
<div className='card-footer d-flex justify-content-between'>
<p className='align-self-center mb-0'>{title}</p>
<h5 className='tex-blue font-italic mb-0'>
<span className='mr-1'>$</span>
{price}
</h5>
</div>
</div>
</ProductWrapper>
);
};
export default Product;
I am also using the onClick element on the product details page which for some reason works while the one on the product page doesn't work. here is the product details code:
import React from 'react';
import { ConsumerContext } from '../Context';
import { Link } from 'react-router-dom';
import { ButtonContainer } from './Button';
const ProductDetails = () => {
return (
<ConsumerContext>
{(value) => {
const { id, company, img, info, price, title, inCart } =
value.detailsProduct;
return (
<div className='container py-5'>
{/* {title} */}
<div className='row'>
<div className='col-10 mx-auto text-center text-slanted text-blue my-5'>
<h1>{title}</h1>
</div>
</div>
{/* {title} */}
{/* {product info} */}
<div className='row'>
<div className='col-10 mx-auto col-md-6 my3'>
<img src={img} alt='product' className='img-fluid' />
</div>
{/* {product text} */}
<div className='col-10 mx-auto col-md-6 my3 text-capitalize'>
<h2>model : {title}</h2>
<h4 className='text-title text-uppercase text-muted mt-3 mb-2'>
made by : <span className='text-uppercase'>{company}</span>
</h4>
<h4 className='text-blue'>
<strong>
price : <span>$</span>
{price}
</strong>
</h4>
<p className='text-capitalize font-weight-bold mt-3 mb-0'>
about product:
</p>
<p className='text-muted lead'>{info}</p>
{/* {buttons} */}
<div>
<Link to='/'>
<ButtonContainer>back to products</ButtonContainer>
</Link>
<ButtonContainer
cartButton
disabled={inCart ? true : false}
onClick={() => {
value.addToCart(id);
// value.openModal(id);
}}
>
{inCart ? 'in cart' : 'add to cart'}
</ButtonContainer>
</div>
</div>
</div>
</div>
);
}}
</ConsumerContext>
);
};
export default ProductDetails;

Can't find the ID in react prop

It's my first day learning react and I'm stuck with an issue (I'm following Mosh's tutorial):
import React, { Component } from "react";
class Counter extends Component {
state = {
value: this.props.value,
};
handleIncrement = () => {
console.log("Click!");
this.setState({ value: this.state.value + 1 });
};
handleDecrement = () => {
console.log("Click!");
if (this.state.value !== 0) {
this.setState({ value: this.state.value - 1 });
}
};
render() {
return (
<div className="row align-items-center">
<div className="col">
<span className={this.getBadgeClasses()}>{this.formatCount()}</span>
</div>
<div className="col">
<button
onClick={() => this.handleIncrement({ id: 1 })}
className="btn btn-dark"
>
+
</button>
<button
onClick={() => this.handleDecrement({ id: 1 })}
className={this.isLessThanZero()}
>
-
</button>
</div>
<div className="col">
<button
onClick={() => this.props.onDelete(this.props.id)}
className="btn btn-danger m-2"
>
Delete
</button>
</div>
</div>
);
}
isLessThanZero() {
let classes = "btn btn-dark ";
classes += this.state.value === 0 ? "disabled" : "";
return classes;
}
getBadgeClasses() {
let classes = "badge m-2 badge-";
classes += this.state.value === 0 ? "warning" : "primary";
return classes;
}
formatCount() {
let { value } = this.state;
return value === 0 ? <h1>Zero</h1> : value;
}
}
export default Counter;
This is a counter component that just responds to the buttons. I'm including those in another component:
import React, { Component } from "react";
import Counter from "./counter";
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 0 },
{ id: 2, value: 0 },
{ id: 3, value: 0 },
{ id: 4, value: 0 },
],
};
handleDelete = (counterId) => {
console.log("Event detected! Delete", counterId);
};
render() {
return (
<div className="cont">
{this.state.counters.map((counter) => (
<Counter
value={counter.value}
key={counter.id}
onDelete={this.handleDelete}
></Counter>
))}
</div>
);
}
}
export default Counters;
In the handleDelete function, when called, I'm getting undefined for the counterId. When I check in the ReactComponents Chrome extention, I see that there isn't any ID:
Why is this happening?
The problem is you are not passing the counter for this.handleDelete. You need to explicitly pass it.
<Counter
value={counter.value}
key={counter.id}
onDelete={() => this.handleDelete(counter.id)}
/>
In the above snippet, I am passing a new function to the Counter component, the function just calls this.handleDelete with the counter.id of the corresponding component.

Categories