React: Card component share same state - reactjs

Created RenderCard component which creates multiple cards for given data.
I am creating bulk selection of card means selecting multiple cards. So when user click on circle present at the top left then that circle become check circle
So here the issue is that all card gets selected when user click on any card. So I want only that card select which users click
const Card = () => {
const [cardSelect, setCardSelect] = useState(false)
const onMouseEnter = () => {
console.log("onMouseEnter1")
}
const onMouseLeave = () => {
console.log("onMouseLeave1")
}
const logMessage = () => {
setCardSelect(prevCheck => !prevCheck);
}
const RenderCard = () => {
return album.map(item => {
return (
<Col className='container' onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} key={item.title} md='2'>
<Card>
<div>
<Link to={`/pages/blog/detail/${item.id}`}>
<CardImg className='img-fluid image' src={item.img} alt={item.title} top />
</Link>
</div>
<div className='select-card'>
{cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />}
</div>
<CardBody>
<CardTitle className="image2 te" >
<b>{item.title}</b>
</CardTitle>
<CardText>{item.comment} Comments</CardText>
</CardBody>
</Card>
</Col>
)
})
}
return (
<>
<Row>
<RenderCard />
</Row>
</>
)
}
Anyone can point me in the right direction where I am doing mistakes.
Thanks

If i understand you correctly i would suggest that
you should wrap anything that is inside the return statement and refactor it to its own component, and then pass item as prop in that component and make the clickhandler inside that component. like this:
const RenderCard = () => {
return album.map(item => {
return ( <Component item={item} />)
}
Now the component have its own state that shows either its clicked or not clicked

With this line of code :
cardSelect ? <CheckCircle onClick={logMessage} /> : <Circle onClick={logMessage} />
You are saying : "If cardSelect is true, then render CheckCircle component, otherwise, render Circle component and do that for all items in album list". So when the state changes, it will render only one type of object CheckCircles, or Circles as many times as there are elements in the album list for which you are calling the map method.
That is why you are seeing all checked circles when you set your variable cardSelect to true.

Related

How to pass active state in React component to use within my tab/toggle buttons

How to pass active state, when a button is clicked in below React component?
I have a component:
<MyLinks links={links}/>
Where I pass this array: const links: CopyType[] = ['link', 'embed'];
// MyLinks component:
const [copyLinkType, setCopyLinkType] = useState<CopyType>();
return (
<React.Fragment>
<ul>
{links.map((linkType) => (
<TabIcon
type={linkType}
onClick={() => {
setCopyLinkType(linkType);
}}
/>
))}
</ul>
{copyLinkType && <TabPanel type={copyLinkType} />}
</React.Fragment>
);
In the frontend you get 2 buttons which show/hide the <TabPanel /> with it's associated content.
How to get/pass an active state when a button is clicked?
I tried passing isActive state as a prop through <TabIcon isActive={isActive} /> and then on the onClick handler setIsActive(true); but this will pass active state to both buttons in the same time?
Try to use refs. Something like this (w/o typescript codepen):
const TabIcon = (props) => {
const {activeRefs, onClick, type, index} = props
return (
<a onClick={()=>{
// On each click change value to the opposite. Or add your
// logic here
activeRefs.current[index] = !activeRefs.current[index]
onClick()
}}>
{type}
</a>
)
}
const MyLinks = (props) => {
const [copyLinkType, setCopyLinkType] = React.useState();
// Array which holds clicked link state like activeRefs.current[index]
const activeRefs = React.useRef([]);
const links = ['link1', 'link2'];
console.log({activeRefs})
return (
<ul>
{links.map((linkType, index) => (
<TabIcon
index={index}
activeRefs={activeRefs}
type={linkType}
onClick={() => {
setCopyLinkType(linkType);
}}
/>
))}
</ul>
);
}
ReactDOM.render(
<MyLinks />,
document.getElementById('root')
);

Allow accordion to only open one at a time

I'm creating an Accordion with react spring, the accordion is ready and working fine, but I can not make it to allow the accordion to open one item at the time, right now open all the three item in the same time, I wish to be able to open only one.
here is the codesand -> https://codesandbox.io/s/prod-sun-ttix7?file=/src/App.tsx
You just have to pull up the open state into the App component and and use the key or index as (open) indicator, like this:
App component
function App() {
const [openKey, setOpenKey] = useState()
const handleToggle = key => {
setOpenKey(openKey !== key ? key : null)
}
return (
<Container>
{data &&
data.map(({ name, content }) => (
<ServiceItem
key={name}
name={name}
content={content}
toggle={handleToggle}
open={openKey === name}
/>
))}
</Container>
);
}
ServiceItem component
const ServiceItem = ({ name, content, toggle, open }: ServiceItemProps): JSX.Element => {
return (
<div key={name}>
<Item onClick={() => toggle(name)}>
<Text className="text-15 regular laptop:text-20 laptop:regular">
{name}
</Text>
<Icon className="text-15 regular laptop:text-20 laptop:regular">
{!open ? "+" : "-"}
</Icon>
</Item>
<Expandable open={open}>
<ContentContainer>
<React.Fragment key={name}>
<Value>{content}</Value>
</React.Fragment>
</ContentContainer>
</Expandable>
</div>
);
};
Here is a working example: https://codesandbox.io/s/infallible-banzai-fnncw.

Render Items from useState Hook React

I have made a call to my api using useEffect and stored the array of items using useState hook but I'm finding it difficult to render those items into a custom component which will also have the data passed.
Here's my react snippets:
export default function CreateCast() {
const [open, setOpen] = useState(false);
const [bibleCastItems, setBibleCastItems] = useState([]);
const classes = useStyles();
const fabStyle = {
bottom: 50.0,
right: 30.0,
position: "fixed"
};
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
// console.log(items);
// console.log(items.data.bibleCasts);
setBibleCastItems([items.data.bibleCasts]);
// items.data.bibleCasts.length > 0 ? setBibleCastItems([items.data.bibleCasts])
// : setBibleCastItems([]);
}
fetchData();
}, []
);
// console.log('bibleCastItems length ' + bibleCastItems.length);
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<Card plain>
<CardHeader plain color="primary">
<div className={classes.container}>
<div className={classes.left}>
<h4 className={classes.cardTitleWhite}>All BibleCasts</h4>
<p className={classes.cardCategoryWhite}>
Powered by our friends from <b>Unicorn Tech Consultants</b>{" "}
</p>
</div>
</div>
</CardHeader>
<CardBody>
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item[index]}/>) // this is where I'm facing issue
// bibleCastItems.map((item, index) => {
// console.log(item);
// setMyItem(item);
// return <div key={index}>{index}</div>
// })
}
<div className={classes.right}>
<Fab style={fabStyle} onClick={handleClickOpen}>
<AddIcon />
</Fab>
<UploadFormDialog
open={open}
handleClose={handleClose}
/>
</div>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
function handleClickOpen(){
setOpen(true);
};
function handleClose(){
setOpen(false);
};
}
Here's my state from browser view:
[![state view][1]][1]
How do I map this state to be a list of components? I'm confused about it
As you can see, I'm using a dialog to create new items and close the dialog once the request is successful. I have one doubt here, how do I tell the main component hosting the dialog that the new data has been fetched and should be added to the state?
My main question here is how to map the items in state to return a list of <CastItem /> component
CastItem Component Snippet
export default function CastItem(props) {
let {bibleCast} = props;
const classes = useStyles();
return <GridContainer>
<GridItem xs={12} sm={6} md={4}>
<Card>
<CardHeader color="info" stats icon>
<CardIcon color="info">
<Streams />
</CardIcon>
</CardHeader>
<CardBody>
<h3 className={classes.cardTitle}>{bibleCast.title}</h3>
<p className={classes.cardCategory}> Reinhard Bonnke</p>
</CardBody>
</Card>
</GridItem>
</GridContainer>
}
CastItem.propTypes = {
bibleCast: PropTypes.object.isRequired,
}
JSON Response from API in console:
[![json response][2]][2]
If you were to create a state variable to represent this response as a list and display that list, how would you go about it, using hooks. Thank you.
[1]: https://i.stack.imgur.com/QkthN.png
[2]: https://i.stack.imgur.com/8Hf11.png
Mistake you are doing is in CreateCast component , form api you are already getting an array again you are passing it inside an array, so it is coming as nested array
Do like this
useEffect(()=>{
async function fetchData(){
var items = await APIService.getAllBibleCasts();
setBibleCastItems(items.data.bibleCasts);
}
fetchData();
}, []
);
For Maping do like this
{
bibleCastItems.map((item, index) => <CastItem key={index} bibleCast={item}/>)
}
// For question how to update parent from child follow below
There are two ways you can set data in a parent component , one is refetch from the api or pass from children to parent and update the state there
I have an example here how to update parent and children,to add names to a list,name list state is maintained in parent component here and child will pass back value to parent by adding name
import { useState } from "react";
import Child from "./Child";
export default function Parent() {
const [list, setList] = useState(["ram"]);
const handleAddName = (name) => {
if (name) {
setList([name, ...list]);
// or you can refetch the list from api here
}
};
return (
<div>
<div style={{ float: "left" }}>
<h1>I am a parent Component</h1>
<ul>
{list &&
list.map((item) => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
<Child handleSubmit={handleAddName} />
</div>
);
}
Child
import { useState } from "react";
export default function Child(props) {
const [name, setName] = useState("");
const updateNameList = (name) => {
if (name) {
props.handleSubmit(name);
//reset field after data is sent
// you can also save data here making post request respective api
setName("");
}
};
return (
<div style={{ float: "right" }}>
<h1>I am a Child Component</h1>
<p> Add names below</p>
<br />
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => updateNameList(name)}>Add</button>
</div>
);
}
refer to this codesand box

How to pass props to Bootstrap OverlayTrigger overlay component?

I created a sandbox: https://codesandbox.io/s/happy-rgb-vks06?file=/src/App.js
I am trying to pass props to the SlotSettings component, but I get this error:
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?
I tried to read both Bootstrap docs and React docs but I could not understand how this should work.
This is the code I'm using:
const SlotSettings = props => {
console.log(props.hello); // this works
return <Popover {...props} id="popover-basic">
<Popover.Title as="h3">Popover right</Popover.Title>
<Popover.Content>
And here's some <strong>amazing</strong> content. It's very engaging.
right?
</Popover.Content>
</Popover>
}
const getDaySlots = slots => {
if (slots.length >= 1) {
return slots.map(slot => {
const variant = slot.status === "free" ? "success" : "secondary";
const buttonRef = createRef();
return (
<OverlayTrigger
key={uuid()}
trigger="click"
placement="bottom"
overlay={<SlotSettings hello="hello" />}
rootClose
>
<Button size="sm" ref={buttonRef} variant={variant} style={{ margin: "8px"}}>{slot.start}</Button>
</OverlayTrigger>
)
});
}
return "No lessons available."
}
I accomplished this by utilizing the popperConfig property of OverlayTrigger.
PopperConfig is used to pass and object to the the underlying popper instance.
link to docs
Simple example:
function renderTooltip(props) {
let message = ""
//Sometimes, props.popper.state is undefined.
//It runs this function enough times that state gets a value
if (props.popper.state) {
message = props.popper.state.options.testObj
}
return (
<Tooltip id="button-tooltip" {...props}>
{message}
</Tooltip>
);
}
function getDaySlots(slots) {
//Other stuff
return (
<OverlayTrigger
placement="right"
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip}
popperConfig={{testObj:"hello there"}}
>
<Button variant="success">Click here</Button>
</OverlayTrigger >
);
}
I messed with your codesandbox, but couldn't get popper to get a state value for some reason. What I posted above works for my project, hope this helps you get started.
import React, { useState ,useRef} from 'react';
import { Popover, Overlay } from 'react-bootstrap';
const DemoComponent = () => {
const [show, setShow] = useState(false);
const target = useRef(null);
return (
<div className="">
<span ref={target} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
open tooltip
</span>
<Overlay target={target.current} show={show} placement="top">
{(props) => (
<Popover id="popover-basic" className="customize-tooltip" {...props}>
<Popover.Body>
put here dyanamic content
</Popover.Body>
</Popover>
)}
</Overlay>
</div>
);}
export default DemoComponent;

Cannot get alert to work when clicking image using React

I am having an issue where I want an alert to popup when I click on an image with React. Unfortunately I cannot figure out why this does not work on when I click on the image. Can someone please help me with this issue?
state = {
cards
}
handleClick = () => {
alert("I have been clicked");
console.log("clicked")
}
render = () => {
return (
<Router>
<div className="App">
<Navbar />
<ImageContainer>
{this.state.cards.map(card => {
return <ImageCard handleClick = {this.handleClick} image={card.image} />
})};
</ImageContainer>
</div>
</Router>
);
}
}
Here is what I have for ImageCard:
export const ImageCard = (props) => (
<Col lg={4}>
<Card>
<Card.Img variant="top" src={props.image} />
</Card>
</Col>
);
You are passing a prop called handleClick to your <ImageCard /> component. Take a look at your <ImageCard />. Once it receives that prop it does nothing with it so it is not being used for an onClick in any way. Try renaming the prop from handelClick to onClick, or pass the handeClick down to the <Card /> or <Col /> component within <ImageCard />.

Resources