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>
Related
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;
I have a parent component that has local state, and pass the setState function to the children.
The children sucessfully changes the parent local stage with the setState function, but the component does not re-render even after this state has change. If I make any change to the state in the parent component, I can see the rendering of both, the modifications made from the child (confirmin that it was actually modifying the local state), and the change made by the parent. But this re-renderin happens only when the change comes from the same component (the parent). I.e, there's no re-rendering if the change comes form any of the children.
Parent component:
const CustomizeItem = ({ item, i }) => {
const [removed, setRemoved] = useState([]);
const [added, setAdded] = useState([]);
const [price, setPrice] = useState(item.price);
const handleRemovable = (removable) => {
if (removed.indexOf(removable) === -1) {
setRemoved([...removed, removable]);
} else {
let removedCopy = [...removed];
removedCopy.splice(removed.indexOf(removable), 1);
setRemoved(removedCopy);
}
};
useEffect(() => {
console.log(added);
console.log(price);
}, [added, price]);
return (
<div className="border-2 ">
<p>
Personaliza la {item.name} Nro {i + 1}:
</p>
<div>
<p>Remover:</p>
{item.removables.map((r, i) => {
return (
<label>
{r.name}
<input
name={r.name}
type="checkbox"
checked={null}
onChange={() => handleRemovable(r)}
/>
</label>
);
})}
</div>
<div className="flex flex-col">
<p>Añadir:</p>
{item.extras.map((extra, i) => {
return (
<ExtraItem
key={i + 1}
extra={extra}
added={added}
setAdded={setAdded}
removed={removed}
/>
);
})}
</div>
<div>
<p>Resumen: </p>
<div>
{g(item, "Un", "Una", "s")} <strong>"{item.name}"</strong>
{removed.length > 0 && (
<p>
{removed.map((r) => (
<p>✖️ Sin {r.name}</p>
))}
</p>
)}
{added.length > 0 && (
<p>
{added.map((a) => (
<p>✔️ Con extra de {a.name}</p>
))}
</p>
)}
</div>
</div>
<p>Precio: ${price}</p>
<button></button>
</div>
);
};
export default CustomizeItem;
Children:
const ExtraItem = ({ added, setAdded, extra }) => {
const [count, setCount] = useState(0);
const addExtra = (extra) => {
let addedCopy = added;
addedCopy.push(extra);
setAdded(addedCopy);
setCount(count + 1);
};
const removeExtra = (extra) => {
let addedCopy = added;
addedCopy.splice(addedCopy.indexOf(extra), 1);
setAdded(addedCopy);
setCount(count - 1);
};
return (
<div className="flex justify-between">
<p>
{extra.name} - ${extra.price}
</p>
<div className="flex gap-2">
<IconButton
variant="outlined"
color="red"
className="flex-none"
size="sm"
onClick={() => removeExtra(extra)}
>
<i class="fa-sharp fa-solid fa-minus" />
</IconButton>
<Typography variant="h4" className="text-justify text-green-400">
{count}
</Typography>
<IconButton
variant="outlined"
color="green"
className="flex-none"
onClick={() => addExtra(extra)}
size="sm"
>
<i className="fas fa-plus" />
</IconButton>
</div>
</div>
);
};
export default ExtraItem;
Note that the useEffect with the dependency of added (in the parent) isn't triggered when the change comes from any of the children...
You can try copying the array and then setting it to the state. Like this:
let addedCopy = [...added];
addedCopy.push(extra);
setAdded(addedCopy);
For some reason, this code works:
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onClick={() => onToggle(task.id)}>
</div>
But this one doesn't:
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onDoubleClick={() => onToggle(task.id)}>
</div>
The rest of the code is this, if it helps:
App.js
const toggleReminder = (id) => {
console.log("double click!", id);
}
return (
<div className="container">
<Header title="Task Tracker"/>
{tasks.length > 0 ? (
<Tasks tasks={tasks} onDelete={deleteTask} onToggle={toggleReminder}/>
) : ("No Tasks to show")
}
Tasks.js
const Tasks = ({ tasks, onDelete, onToggle }) => {
return (
<>
{tasks.map((task, index) => (
<Task key={index} task={task} onDelete={onDelete} onToggle={onToggle} />
))}
</>
)
}
And Task.js
const Task = ({task, onDelete, onToggle}) => {
return (
<div className="task"
onDoubleClick={() => onToggle(task.id)}>
<h3>{task.text}
<FaTimes
style={{color:"red", cursor: 'pointer'}}
onClick={()=> onDelete(task.id)}/>
</h3>
<p>{task.day}</p>
</div>
)
}
I've been copying everything from a tutorial, and I really don't understand the problem. With onClick it works just fine. Do they work differently?
I saw a similar question but did not understand the answer
Thank you!
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/>
);
};
const ListView = () => {
return(
<ul>
<ListItem modal={<Modal />} />
</ul>
)
};
const ListItem = (props) => {
const [visible, setVisible] = useState(false);
const toggle = () => setVisible(!visible)
return (
<>
<li>
ListItem
</li>
<ModalWrapper toggle={toggle}>{props.modal}</ModalWrapper>
</>
)
}
const ModalWrapper = (props) => {
if(!props.visible) return null;
return (
<>
{props.children}
</>
)
}
const Modal = ({ toggle }) => {
/* I would like to use toggle() here. */
return (
<>
<div onClick={toggle} className="dimmer"></div>
<div className="modal">modal</div>
</>
)
}
I have a function toggle() in <ListItem /> as shown above.
I am struggling to use toggle() in <Modal />.
Is it possible or are there any suggestions?
You need to inject toggle to ModalWrapper children, be careful not to override toggle prop on Modal after it.
const ModalWrapper = ({ children, visible, toggle }) => {
const injected = React.Children.map(children, child =>
React.cloneElement(child, { toggle })
);
return <>{visible && injected}</>;
};
Refer to React.cloneElement and React.Children.map.
Demo: