React Hooks: toggle element on map function - javascript

See that loop loading two components. I would like to display only <Image /> by default, but when I click this element, I want it to turn into <YouTube /> (only the one I press, the others are still <Image />). I can do this on a class component, but I want to use a hook
export const MusicVideos = () => {
const [selectedVideo, setVideo] = useState(0);
return (
<Wrapper>
{videos.map(video => (
<div key={video.id}>
<Image src={video.image} hover={video.hover} alt="thumbnail" />
<YouTube link={video.link} />
</div>
))}
</Wrapper/>
);
};

you can bind onClick for your image and setVideo to video.id and compare with video.id to render image or video.
export const MusicVideos = () => {
const [selectedVideo, setVideo] = useState(0);
return (
<Wrapper>
{videos.map(video => (
{selectedVideo !== video.id ?
<Image onClick={() => setVideo(video.id) src={video.image} hover={video.hover} alt="thumbnail" /> :
<YouTube link={video.link} />
))}
</Wrapper/>
);
};

Create a component like this and pass it to the loop;
const YouTubeToggle = (video) => {
const [selectedVideo, setVideo] = useState(0);
return (
<div key={video.id}>
{selectedVideo == 0 &&
<Image src={video.image} onClick={() => setVideo(!selectedVideo)} hover={video.hover} alt="thumbnail" />
}
{selectedVideo != 0 &&
<YouTube link={video.link} />
}
</div>
);
}
export const MusicVideos = () => {
const [selectedVideo, setVideo] = useState(0);
return (
<Wrapper>
{videos.map(video => (
<YouTubeToggle video={video} />
))}
</Wrapper/>
);
};

Related

How can I map through Props consisting of 3 useMemo functions in React?

I have 3 separate functions using useMemo that return a different type of alert for a user in a dashboard.
These alerts appear at the top of the user dashboard if they are truthy, but if there is more than 1 alert that should appear, they are placed within a slideshow that slides automatically.
I have created a Slideshow component and pass all 3 functions in through props. My question is how I can map through props in the Slideshow component to render out these alerts?
I have tried to do so with 'props.map' but receive an error about map missing from PropTypes so am unsure if I am doing this correctly by trying to render out the alerts with a .map?
This is my main file with the 3 useMemo alerts:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() => {
if (accountInfo?.exceed_tx_capability)
return (
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() => {
if (unlinkedTransferTx?.items?.length > 0)
return (
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
);
}, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() => {
if (suggestedLinks?.items?.length > 0)
return (
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
);
}, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow
renderMaxManualTransactionWarning={renderMaxManualTransactionWarning}
renderMissingTransferTransactionWarning={renderMissingTransferTransactionWarning}
renderSuggestedLinksWarning={renderSuggestedLinksWarning}
/>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
And my SlideShow component:
/* eslint-disable no-unused-vars */
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = (props) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{/* Map through the functions and render them here */}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;
Put simply, I just want to map through the functions coming from props and render them in the Slideshow.
Any advice on this would be appreciated. Many thanks in advance!
Instead of pass the alerts as a props you can do it as a children
1:
export const TransactionsPage = (props) => {
const { t } = props;
const { data: profile } = apiService.useGetSingleAccountQuery();
const { data: accountInfo } = apiService.useGetPlanQuery();
const renderMaxManualTransactionWarning = useMemo(() =>
accountInfo?.exceed_tx_capability ?
<>
<div className={"free-trial-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>{t("pages.transactions.warning.transactions_limit")}</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/account/subscription/change_plan")}
>
{t("pages.transactions.warning.action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, accountInfo]);
// }, [t]);
const renderMissingTransferTransactionWarning = useMemo(() =>
unlinkedTransferTx?.items?.length > 0 ?
<>
<div className={"import-warning"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.transfer_transactions_missing").replace(
"[num]",
unlinkedTransferTx.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/transfer/list")}
>
{t("pages.transactions.warning.transfer_transactions_missing_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
</>
: null
, [t, unlinkedTransferTx]);
const renderSuggestedLinksWarning = useMemo(() =>
suggestedLinks?.items?.length > 0 ?
<div className={"suggested-links-info"}>
<div className={"row"}>
<CryptoIcon name={"alert"} />
<Text size={14}>
{t("pages.transactions.warning.suggested_links").replace(
"[num]",
suggestedLinks.items.length
)}
</Text>
</div>
<Button
size={"small"}
type={"primary"}
onClick={() => historyObject.push("/transactions/links/suggestions")}
>
{t("pages.transactions.warning.links_action")}
<CryptoIcon name={"arrow-right"} />
</Button>
</div>
:null
, [t, suggestedLinks]);
return (
<LoggedInLayout className={"transactions-page"}>
<Slideshow>
{renderMaxManualTransactionWarning}
{renderMissingTransferTransactionWarning}
{renderSuggestedLinksWarning}
</Slideshow>
</LoggedInLayout>
);
};
TransactionsPage.propTypes = {
t: func,
};
export default withTranslation()(TransactionsPage);
2:
import React, { useEffect, useState, useRef, useMemo } from "react";
import "./index.scss";
import PropTypes from "prop-types";
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 10000;
export const Slideshow = ({children}) => {
const [index, setIndex] = useState(0);
const timeoutRef = useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() => setIndex((prevIndex) => (prevIndex === colors.length - 1 ? 0 : prevIndex + 1)),
delay
);
return () => {
resetTimeout();
};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{children}
</div>
<div className="slideshowDots">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshowDot${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
};
Slideshow.propTypes = {
};
export default Slideshow;

Thumbnail click event for antd carousel

Hi all I have following code
const App = () => {
const mediaRef = useRef(null);
const navRef = useRef(null);
const [direction, setDirection] = useState(null);
const onChange = (currentSlide) => {
if (direction === "next") {
mediaRef.current.goTo(currentSlide + 1, false);
} else {
mediaRef.current.goTo(currentSlide - 1, false);
}
};
const handleNext = () => {
setDirection("next");
navRef.current.next();
};
const handlePrev = () => {
setDirection("prev");
navRef.current.prev();
};
const imageList = [ some images array ];
return (
<>
<>
<Carousel
asNavFor={navRef.current}
ref={mediaRef}
>
{imageList?.map((el, id) => (
<ImageWrapper key={id}>
<img src={el} alt={"name"} />
</ImageWrapper>
))}
</Carousel>
{imageList?.length > 1 && (
<>
<Button onClick={handlePrev}>
<LeftOutlined />
</Button>
<Button onClick={handleNext}>
<RightOutlined />
</Button>
</>
)}
</>
{imageList?.length > 1 && (
<Carousel
slidesToShow={3}
centerMode={true}
asNavFor={mediaRef.current}
ref={(carousel) => (navRef.current = carousel)}
beforeChange={onChange}
>
{imageList?.map((el, id) => (
<>
<divkey={id}>
<img src={el} alt={"name"} />
</div>
</>
))}
</Carousel>
)}
<>
<Button onClick={handlePrev}>
<LeftOutlined />
</Button>
<Button onClick={handleNext}>
<RightOutlined />
</Button>
</>
</>
);
};
I am doing following, I am taking antd carousel adding another carousel for thumbnails and putting custom arrows, clicking on next and on previouse buttons it automatically change main image and thumbnail.
Now I am trying to put onClick event on thumbnails, I mean Thumbnails should be clickable and by click, the large image and thumbnail should be changed. How can I achieve that ?
You can add a click handler on the thumbnail with the clicked id as parameter
<ThumbnailWrapper key={id}>
<img
src={el}
alt={"name"}
onClick={() => thumbnailClicked(id)}
/>
</ThumbnailWrapper>
then do something with the id in the defined function:
const thumbnailClicked = (id) => {
console.log("thumbnail clicked:",id);
//then e.g. set parent the same as the thumbnail.
mediaRef.current.goTo(id, false);
};

React Toggle Hook State

I tried to toggle individual item but unfortunately whenever I try to toggle an item the other item gets affected. Here is my code:
const FAQ = () => {
const [open, setOpen] = useState(false);
const [data, setData] = useState(faqData);
return (
<FAQSection>
<FAQTitle>Frequently Asked Questions</FAQTitle>
<Questions>
<QuestionItemDetail>
{data.map((item) => {
const { id, question, answer } = item;
return (
<div key={id}>
<QuestionItem onClick={() => setOpen(!open)}>
<QuestionItemTitle>{question}</QuestionItemTitle>
{open ? <Close /> : <Add />}
</QuestionItem>
<ReadQuestion>
{open && (
<ReadQuestionDetail>
<ReadQuestionDesc>{answer}</ReadQuestionDesc>
</ReadQuestionDetail>
)}
</ReadQuestion>
</div>
);
})}
</QuestionItemDetail>
</Questions>
</FAQSection>
);
};
What might be wrong with this because I ensured the dummy data has a unique ID.
Because you use a boolean to control all open/close. You need to use index/id to control this.
const [open, setOpen] = useState(null);
...
onClick={() => setOpen(preOpen => preOpen === id ? null : id)}
...
{open === id && (<ReadQuestionDetail>...</ReadQuestionDetail>)}
Your open state is used for all of the items in your data array, which is why it affects all of the items when toggled.
I recommend:
putting all of the data item html/jsx inside a new component.
Inside this new component, create an open state like so:
const MyItemComponent = (id, question, answer) => {
const [open, setOpen] = useState(false);
return (
<div key={id}>
<QuestionItem onClick={() => setOpen(!open)}>
<QuestionItemTitle>{question}</QuestionItemTitle>
{open ? <Close /> : <Add />}
</QuestionItem>
<ReadQuestion>
{open && (
<ReadQuestionDetail>
<ReadQuestionDesc>{answer}</ReadQuestionDesc>
</ReadQuestionDetail>
)}
</ReadQuestion>
</div>
);
}
const FAQ = () => {
const [data, setData] = useState(faqData);
return (
<FAQSection>
<FAQTitle>Frequently Asked Questions</FAQTitle>
<Questions>
<QuestionItemDetail>
{data.map((item) => {
const { id, question, answer } = item;
return (
<MyItemComponent id={id} question={question} answer={answer} />
);
})}
</QuestionItemDetail>
</Questions>
</FAQSection>
);
};
This will give you an individual open state for each item.

pass a prop as a string, then convert string back into javascript

I am trying to build a reusable carousel that will have much different conditions.
So i need to be able to set this "imageQuery" on my display page, sending it as a string, and then in my carousel, render it as usable javascript
the query works if I just hard code it in, I am just trying to build upon it
My display page has this
const imageQuery = 'arts.fineartfields.finehome === "Yes"';
<MyCarousel
finearts={finearts}
imageQuery={imageQuery}
/>
my carousel page has this
export default function MyCarousel({ finearts, imageQuery }) {
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
return (
<Carousel
activeIndex={index}
onSelect={handleSelect}
className="carousel-fade">
{finearts.nodes && finearts.nodes.map((arts) => (
{imageQuery} ?
<Carousel.Item className="" key={arts.databaseId}>
<Image src={arts.fineartfields.cloudlink}
alt={arts.featuredImage.node.altText}
className="carousel-image img-fluid shadow-sm"
width={arts.featuredImage.node.mediaDetails.width}
height={arts.featuredImage.node.mediaDetails.height}
/>
</Carousel.Item>
: null
), [])}
</Carousel>
)
}
You don't need to pass imageQuery props
<MyCarousel finearts={finearts} />
Carousel component
export default function MyCarousel({ finearts }) {
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
return (
<Carousel
activeIndex={index}
onSelect={handleSelect}
className="carousel-fade">
{finearts.nodes && finearts.nodes.map((arts) => (
arts.fineartfields.finehome === "Yes" ? (
<Carousel.Item className="" key={arts.databaseId}>
<Image src={arts.fineartfields.cloudlink}
alt={arts.featuredImage.node.altText}
className="carousel-image img-fluid shadow-sm"
width={arts.featuredImage.node.mediaDetails.width}
height={arts.featuredImage.node.mediaDetails.height}
/>
</Carousel.Item>
) : null
), [])}
</Carousel>
)
}

How can I pass the Child name prop to Parent array

I have a list of Child objects mapped from my Parent component in my React App.
When a Child item is clicked, I need the props.name of that item to be pushed to the selectedItems array in the Parent component via the handleClick function.
How can I achieve this?
function Parent() {
let selectedItems = [];
const result = Data.filter((e) => selectedItems.includes(e.id));
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{Data.map((item, i) => {
return <Child
key={item.id}
name={item.name} />
})}
</main>
</div>
);
}
export default App
const Child = (props) => {
const [clickCount, setClickCount] = useState(0);
function handleClick() {
setClickCount(prevClickCount => prevClickCount + 1);
}
return (
<div
className="product"
onClick={() => handleClick()}
>
<p>{props.name}</p>
<p>{clickCount > 0 ? <p>Selected: {clickCount}</p> : <p>Not Selected</p>}</p>
<img src={props.img} alt="" />
</div>
);
}
I would recommend using hooks for the 'selectedItems' in the parent component, as to trigger a re-render when the name changes.
You can pass functions from the parent to the child component using props.
Below I've passed the 'addToSelectedItems' function down to the child and triggered it in the handleClick method.
const Parent = () => {
const [selectedItems, setSelectedItems] = useState([]);
function addToSelectedItems(name){
setSelectedItems([...selectedItems, name]);
}
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{Data.map((item, i) => {
return <Child
key={item.id}
name={item.name}
addToSelectedItems={addToSelectedItems}
/>
})}
</main>
</div>
);
}
export default App
const Child = (props) => {
const [clickCount, setClickCount] = useState(0);
function handleClick(name) {
setClickCount(prevClickCount => prevClickCount + 1);
props.addToSelectedItems(name);
}
return (
<div
className="product"
onClick={(props.name) => handleClick(props.name)}
>
<p>{props.name}</p>
<p>{clickCount > 0 ? <p>Selected: {clickCount}</p> : <p>Not Selected</p>}</p>
<img src={props.img} alt="" />
</div>
);
}
I'd do something like this, but I have not tested it, it might need some fine tuning
function Parent() {
let selectedItems = [];
const result = Data.filter((e) => selectedItems.includes(e.id));
const [clickCount, setClickCount] = useState(0);
function handleClick(name) {
setClickCount(prevClickCount => prevClickCount + 1);
selectedItem.push(name)
}
return (
<div className="App">
<main className="products-grid flex flex-wrap">
{Data.map((item, i) => {
return <Child
key={item.id}
name={item.name}
clickCount={clickCount}
handleClick={handleClick} />
})}
</main>
</div>
);
}
export default App;
const Child = (props) => {
let { handleClick, name, clickCount, img } = props
return (
<div
className="product"
onClick={() => handleClick(name)}
>
<p>{name}</p>
<p>{clickCount > 0 ? <p>Selected: {clickCount}</p> : <p>Not Selected</p>}</p>
<img src={img} alt="" />
</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>

Categories

Resources