I am building my website portfolio using React and Sanity. This actually is my first project with React. The idea was to use sanity in order to store data that I can use on my website, such as "projects" and so far everything is going well, except for one thing: THE BUTTON IS TARGETING WRONG DATA.
The projects are divided in categories: UX/UI - React - JavaScript - University Projects - All
Everything is working fine, the tags imported from sanity's schemas allow me to categorise the projects.
Every project looks like a little card and when hovered, there is a little description as long as the button "MORE+".
HERE IS THE PROBLEM
When I click the button, there is a big window showing up where I can see what is the project about.
Right now there are two projects on sanity (let's call them A and B).
Project A is categorised as JavaScript and project B as React and UI/UX.
If I hover on project A and Click the button "MORE+", it would open project B on the big window, why is that?
This happens only when I am in the category "ALL" but I assume it doesn't happen in other categories only because there is only one project each category, while in "ALL" both projects are shown.
I leave below the code that I used for the button and how I imported this from sanity.
It may look a bit confusing and long, only because I used a lot of motion frame and wrapped everything in a lot of div
Also in few point it is still uncomplete.
import React, { useState, useEffect } from 'react';
import {AiFillEye, AiFillGithub} from 'react-icons/ai';
import {motion} from 'framer-motion';
import './Work.scss';
import { HiX } from 'react-icons/hi';
import { AppWrap } from '../../wrapper';
import {urlFor, client} from '../../client';
const Work = () => {
const [works, setWorks] = useState([]);
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
const [animateCard, setAnimateCard] = useState({ y: 0, opacity: 1 });
const [toggle, setToggle] = useState(false);
useEffect(() => {
const query = '*[_type == "works"]';
client.fetch(query).then((data) => {
setWorks(data);
setFilterWork(data);
});
}, []);
const handleWorkFilter = (item) => {
setActiveFilter(item);
setAnimateCard([{ y: 100, opacity: 0 }]);
setTimeout(() => {
setAnimateCard([{ y: 0, opacity: 1 }]);
if (item === 'All') {
setFilterWork(works);
} else {
setFilterWork(works.filter((work) => work.tags.includes(item)));
}
}, 500);
};
return (
<>
<h2 className="portfolio-head-text">My <span>Portfolio</span></h2>
<div className="app__work-filter">
{['UI/UX','JavaScript', 'React JS', 'University Projects', 'All'].map((item, index) => (
<div key={index}
onClick={() => handleWorkFilter(item)}
className={`app__work-filter-item app_flex p-text ${activeFilter === item ? 'item-active' : ''}`}>
{item}
</div>
))}
</div>
<motion.div
animate={animateCard}
transition={{duration:0.5, delayChildren: 0.5}}
className="app__work-portfolio"
>
{filterWork.map((work,index) => (
<div className="app__work-card-container" key={index}>
<div className="app__work-item app__flex">
<div className="app__work-img app__flex">
<img src={urlFor(work.imgUrl1)} alt={work.name}/>
<motion.div
whileHover={{opacity:[0,1]}}
transition={{duration: 0.3, ease: 'easeInOut', staggerChildren: 0.6}}
className="app__work-hover app__flex">
<p>{work.descriptionPreview}</p>
<motion.div
whileInView={{scale:1}}
whileHover={{scale:[1,0.9]}}
transition={{duration: 0.2}}
className="app__flex"
>
<button onClick={() => setToggle(true)}>more+</button>
</motion.div>
</motion.div>
</div>
<div className="app__work-content app__flex">
<h4 className="bold-text">{work.title}</h4>
<p className="p-text" style={{marginTop: 10}}>{work.tagView}</p>
</div>
</div>
{toggle &&(
<div className="app__work-big-window">
<div className="window-img-x">
<img classname="window-img" src={urlFor(work.imgUrl1)} alt={work.name}/>
<div><HiX className="window-x" onClick={() => setToggle(false)}/></div>
</div>
<div>
<h4>{work.title}</h4>
<h6>{work.subTitle}</h6>
<div/>
<p>{work.description}</p>
</div>
<div/>
<div>
<h6>Technologies used: </h6>
<p>{work.tech}</p>
</div>
</div>
)}
</div>
))}
</motion.div>
</>
)
}
Related
This question already has an answer here:
React router 6 never unmount component when URL parameter changes
(1 answer)
Closed 13 days ago.
I am making an ecommerce website. If I click a product, the product full description will be displayed in a new page called detailPage and there will be similar product fetched from the API at the bottom of the detailPage. It is working fine till here. But if I click any of the similar product, I want the similar product to be displayed in the detailPage. How can I make it work?
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import useFetch from '../../Component/UseFetch/useFetch'
import { Link } from 'react-router-dom'
import './detailPage.css'
const DetailPage = () => {
const [item, setItem] = useState([])
const [mainImg, setMainImg] = useState('')
const [sameProduct, setSameProduct] = useState([])
const { loading, products } = useFetch('https://dummyjson.com/products')
const { id } = useParams()
useEffect(()=>{
const product = products.find(product => product.id === Number(id))
if(product){
setItem(product)
setMainImg(product.thumbnail)
}
},[products])
useEffect(()=>{
const allProduct = products.filter(product => product.category === item.category)
setSameProduct(allProduct)
}, [item, products])
return (
loading ? <div style={{
width: '100vw',
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '10vw'
}}>Loading...</div> :
<div>
<div>
<h1 className='product-headline'>Product Detail</h1>
</div>
<div className='product-box'>
<div>
{
products && item && (
<div style={{height: '320px'}}>
<img className='main-image' src={mainImg} />
</div>
)
}
<div className='short-img-box'>
{
products && item && item.images && item.images.map((image, id)=>{
return <img className='short-img' key={id} src={image} onClick={()=> setMainImg(image) }/>
})
}
</div>
</div>
<div>
{
products && item && item.price && !isNaN(item.price) && (
<div>
<h1>{item.title}</h1>
<div>
<span>Rating: {item.rating}</span>
<span>Available Stock: {item.stock}</span>
</div>
<div>
<span>{parseInt(item.price + item.price / 100 * 25)}</span><br></br>
<span>{item.price}</span>
</div>
<p>{item.description}</p>
</div>
)
}
<button>Add to cart</button>
</div>
</div>
<div>
<h1 style={{textAlign: 'center'}}>Review section</h1>
</div>
<div>
<h1 className='product-headline'>Similar Products</h1>
<div className='products'>
{
sameProduct.map((product)=>{
const {id, title, description, category, price, thumbnail, images, rating, stock} = product
return (
<div key={id} className='product'>
<Link to={`/detailpage/${id}`}><img src={thumbnail} /></Link>
<div className='product-details'>
<Link className='title' to={`/detailpage/${id}`}><h3>{title}</h3></Link>
<div className='price-rating'>
<p>${price}</p>
<p>Rating: {rating}/5</p>
</div>
</div>
<button className='add-cart'>ADD TO CART</button>
<span className='product-stock'>Stock: {stock}</span>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default DetailPage
It looks to me that your only problem is that id is not included in your useEffect's dependency array. Can you confirm that that the url is updated to the new product id when you click the similar product?
In that case, all you should do is include the id in the dependency array like this to make the useEffect re-run when the id in the URL is changed:
useEffect(()=>{
const product = products.find(product => product.id === Number(id))
if(product){
setItem(product)
setMainImg(product.thumbnail)
}
},[products, id])
I think a good solution for this should be something like this (not find product in array of product, but fetch single product in single request (for details page):
You will add useEffect hook to handle, when id of product changed in URL.
useEffect(()=> {
// i change name of the function, beacuse it's impossible to use hooks inside callbacks
const {loading, poduct} = someFetchFunction('https://dummyjson.com/products');
// save your data in `useReducer` hook (but it's up to you)
}, [id]);
So right now when you change url on this page, this useEffect hook will trigger and get current product
You also have a way to change the useFetch hook, and move useEffect to this hook, so when something changes it will trigger a request, but from my perspective of view my first variant is better
i am trying to save favorite post ids in an array with button click. The thing is currently it is only saving one ID at a time in array and when you click you on another "click here" button , it removes the previous id and show you the current id. Currently my array is not saving previous saved Ids with the new one. I will appreciate it if someone explain why it is not working and is there something wrong in my code while saving the ids.
Screenshot
Code
import React, { useEffect } from 'react'
import Demopic from "../assets/img/demopic/4.jpg";
import { useState, useRef } from 'react';
import { post } from 'jquery';
export default function Post(props) {
const { id, title, body } = props.data;
const [postid,setPostID] = useState([]);
const [isActive, setActive] = useState(false);
function onHandleOnClick(id) {
setPostID([...postid, id]);
};
useEffect(()=>{
console.log(postid);
},[postid]);
return (
<div className="card">
<div className="row">
<div className="col-md-5 wrapthumbnail">
<a href="post.html">
<div className="thumbnail dd" style={{ backgroundImage: 'url(' + Demopic + ')' }}>
</div>
</a>
</div>
<div className="col-md-7">
<div className="card-block">
<h2 className="card-title"><a href={'/post-detail/'+id} key={id}>{title}</a></h2>
<h4 className="card-text">
{body.length > 100 ? `${body.substring(0, 90)}...` : body }
</h4>
<div className="metafooter">
<div className="wrapfooter text-left">
<span className="meta-footer-thumb">
<a href="author.html">
<img className="author-thumb" src="https://www.gravatar.com/avatar/e56154546cf4be74e393c62d1ae9f9d4?s=250&d=mm&r=x" alt="Sal"/></a>
</span>
<span className="author-meta">
<span className="post-name">Steve</span><br/>
<span className="post-date">22 July 2017</span><span className="dot"></span><span className="post-read">6 min read</span>
</span>
<button onClick={(e)=>onHandleOnClick(id)} value={id}>click here</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
looks like your Post component is a single post, therefore each one of the Post components that you are creating will have their own postId array.
so if you have 10 posts you will have 10 different arrays, to test this, you can click on the same button multiple times and you will see that they array of that component will grow, for example the Id is 4 and you click multiple times you will see [4 ,4, 4, 4.....] and then if you do it on the Id 2 you will see [2, 2, 2, 2....].
to fix this you should have on your parent component the array and then in then pass the update function to your childs, something like this:
const ParentComponent = () => {
const [postid,setPostID] = useState([]);
const [isActive, setActive] = useState(false);
function onHandleOnClick(id) {
setPostID([...postid, id]);
};
useEffect(()=>{
console.log(postid);
},[postid]);
return (<>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
</>)
}
and then in your child component (Post) you should remove the state and the handler and just use the parent function:
function Post(props) {
const { id, title, body } = props.data;
const { clickFavourite } = props;
return (
<button onClick={(e)=>clickFavourite(id)} value={id}>click here</button>
)
}
notice that I deleted a lot of the content of the Post component just to read it easily.
also take into account that this can be done in different ways, this was the first way that came into my mind but you can use global states, states managers, hooks, etc etc. but as far as I see this is the easiest way and will get the job done
Use
const [postid,setPostID] = useState({});
Instead of
const [postid,setPostID] = useState([]);
It will work.
I followed a tutorial recently about integrating a cms into your website. The tutorial used sanity cms which made the process very intuitive. Once I was done with the tutorial I was ready to use it in my own projects.
however when I try to fetch data with the useEffect hook I get an error: Cannot read properties of undefined. I know this is because fetching data is done async. But the thing I can't wrap my head around is I did it the exact same way as the tutorial. He didn't use any state for loading or isFetched. So my question is what did I do different than the tutorial and how should I solve it?
I don't really want to use a loading state because that doesn't really look that good...
This is the JSON object I receive from the api:
[{…}]
0:
buttonlabel: "Start learning"
description: "Ranging from beginner to pro level tricks. Wanna know the best way to learn a trick? You can search for it down below and find a tutorial from one of our trainers as well as a detailed explanation. Still stuck? Come ask us at a Westsite training moment."
intro: "Welcome to the Westsite trick progression guide. Here you can find a collection of all the wakeboarding tricks you can think of. "
_createdAt: "2022-05-24T16:26:13Z"
_id: "a4f8cf02-4b86-44d5-a63d-c95a3a7d3293"
_rev: "QYLgvM20Eo53w3noOOj0MB"
_type: "hero"
_updatedAt: "2022-05-24T17:29:10Z"
This is the tutorial component:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { urlFor, client } from "../../client";
import { AppWrap, MotionWrap } from "../../wrapper";
import "./About.scss";
const About = () => {
const [abouts, setAbouts] = useState([]);
useEffect(() => {
const query = '*[_type == "abouts"]';
client.fetch(query).then((data) => setAbouts(data));
}, []);
return (
<div>
<h2 className="head-text">
I know that
<span> Good Design </span>
<br />
means
<span> Good Business</span>
</h2>
<div className="app__profiles">
{abouts.map((about, index) => {
return (
<motion.div
whileInView={{ opacity: 1 }}
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.5, type: "tween" }}
className="app__profile-item"
key={about.title + index}
>
<img src={urlFor(about.imgUrl)} alt={about.title} />
<h2 className="bold-text" style={{ marginTop: 20 }}>
{about.title}
</h2>
<p className="p-text" style={{ marginTop: 10 }}>
{about.description}
</p>
</motion.div>
);
})}
</div>
</div>
);
};
export default AppWrap(
MotionWrap(About, "app__about"),
"about",
"app__whitebg"
);
And this is mine:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
client.fetch(query).then((data) => setHeroContent(data));
}, []);
const content = heroContent[0];
return (
<div className="app__hero">
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>
</div>
);
};
export default Hero;
This line will cause issues before the data is applied to state asynchronously
const content = heroContent[0];
On the initial render, heroContent is an empty array, so content will be undefined until your data is loaded. A couple options -
1 - render some sort of loading state until heroContent has been populated -
if (!heroContent.length) return <LoadingSpinner />
2 - wrap the portion that is trying to use content with a guard clause
{content && (
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
)}
The issue comes when you try to access properties from content when it's undefined. If you don't want to show any loading indicator, I would go with showing some fallback for when content is not defined. e.g.
Instead of:
<p className="p-text">{content.intro}</p>
You could go with:
<p className="p-text">{content?.intro ?? '-'}</p>
or something like that.
Problem is that you breakdown your state on different level that create problem with state changes. So, you have to do this
Either you call state as map function or save your state with specfic index 0.
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
// this is giving response as array
client.fetch(query).then((data) => setHeroContent(data));
}, []);
return (
<div className="app__hero">
{heroContent.map((content,index)=>
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
key={index}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>}
</div>
);
};
export default Hero;
I have implemented the react-collapsed library to make a collapse button works. I need to implement this button inside a loop, but I didn't find a way to make it works properly inside a loop. If I press the 1 button to expand 1 section, it expands every section in the loop, so it's not working well.
import React from "react";
import AllIcons from "../../ui/icons/all-icons";
import TableSellDataCard from "./TableSellDataCard";
import useCollapse from 'react-collapsed';
const TableContent = ({ data, columns, }) => {
const { getCollapseProps, getToggleProps, isExpanded } = useCollapse();
return (
<>
{data.map((item, index) => {
return (
<div key={index}>
<div className="flex px-5 border-b border-gray-form py-2" data-reservation-id={item.id}>
{columns.map(() => {
return (
<div>
<button className="accordion" {...getToggleProps()}>
{isExpanded ? <AllIcons name="AccordeonArrow" /> : <AllIcons name="AccordeonArrowUp" />}
</button>
</div>
);
})}
</div>
<section {...getCollapseProps()}><TableSellDataCard /></section>
</ div>
);
})}
</>
);
};
export default TableContent;
I have one button that toggles the section, but it expands all the sections inside the loop. I need to make it work. Button 1 only opens section 1, and button 2 only opens section 2.
this is my first react project by using firebase, everything is correct which all upload function works very well, also all images can be shown, but i use firebase.firestore.collection('image').document(doc.id).delete() when i want to delete one of the images, it shows an error which is:
Uncaught TypeError: db.document is not a function
I do not know what is going on, can someone help to resolve it, please?
projectFirestore = firebase.firestore() in the firebase config file.
import React from 'react';
import useFirestore from '../hooks/useFirestore';
import { motion } from 'framer-motion';
import { projectFirestore } from '../firebase/config';
const ImageGrid = ({ setSelectedImg }) => {
const { docs } = useFirestore('images');
const db = projectFirestore.collection('image');
return (
<div className="img-grid">
{docs &&
docs.map(doc => (
<motion.div
className="img-wrap"
key={doc.id}
layout
whileHover={{ opacity: 1 }}
onClick={() => setSelectedImg(doc.url)}
>
<motion.img
src={doc.url}
alt="uploaded pic"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
/>
<button className='showBt' onClick={() => db.document(doc.id).delete()}>-</button>
</motion.div>
))}
</div>
);
};
export default ImageGrid;
The method you're looking for is doc(), not document().