How to make links work with card-img-overlay React - reactjs

I'm having an issue on my project. I created a card-img-overlay to display icons over an image. If you click on the entire image you are redirected to a post. I would like to make the like and share icons clickable.
My project is in Reactjs. I am displaying images and videos from Reddit API.
Thank you for your help.
id,
slugTitle,
title,
url_overridden_by_dest,
author,
preview,
}) => {
const [isVideo, setIsVideo] = useState(false);
useEffect(() => {
if (preview) setIsVideo(preview.split('.').pop() === 'mp4');
}, [preview]);
const history = useHistory();
const goToPage = () => {
history.push(`/Post/${id}/${slugTitle}`);
};
return (
<Card
inverse
onClick={goToPage}
style={{
cursor: 'pointer',
}}
>
{isVideo && (
<video autoPlay="false" loop width="100%" src={preview}>
<track default kind="captions" />
</video>
)}
{!isVideo && (
<CardImg top width="100%" src={url_overridden_by_dest} alt={title} />
)}
<CardImgOverlay className="hideinfos">
<CardText className="w-100 d-flex justify-content-between">
<div>
<VscAccount className="mr-2" size={20} />
{author}
</div>
<div>
<LikeButtonhp
className="mr-2 card-link"
size={20}
style={{
position: 'relative',
}}
/>
<BiShareAlt size={20} />
</div>
</CardText>
</CardImgOverlay>
</Card>
);
};

You'll need to put onClick handlers on your LikeButtonhp and BiShareAlt components, and use event.stopPropagation() to stop the event from bubbling up to the <Card />:
<BiShareAlt
size={20}
onClick={event => {
event.stopPropagation();
// Do stuff for share click
}}
/>
You may need to alter the BiShareAlt and LikeButtonhp components to support an onClick prop also, for example if they render a <button> element it may look like this:
const BiShareAlt = ({ onClick }) => (
<button onClick={onClick}>
Share
</button>
);
export default BiShareAlt;

In my onClick, I added an e.stopPropagation(); and it solves my problem. Now I can click on the heart icon and it works. It stops the onClick set up on my image (parent).
function LikeButtonhp() {
const [liked, setLiked] = useState(false);
return (
<Button
outline
color="link"
className="likebutton"
onClick={(e) => {
e.stopPropagation();
setLiked(!liked);
}}
style={{ color: 'white' }}
>
{liked ? <BsHeartFill size={20} /> : <BsHeart size={20} />}
</Button>
);
}

Related

Inline media queries using react

I have this section of code I am trying to render. At different viewports, the button styles change from underline to background color using a themeColor I pull from a JSON file with acting as my database in a sense. How do you write an 'else if' inline in react so when my state is "active" and the media query matches, the styles update accordingly?
const btnMobileStyle = useMediaQuery('(min-width: 375px)')
const btnTabletStyle = useMediaQuery('(min-width: 768px)')
const styles = {
buttonMobile: (btnMobileStyle) => ({
borderBottom: `2px solid ${themeColor}`,
}),
buttonTablet: (btnTabletStyle) => ({
borderBottom: 'none',
backgroundColor: `${themeColor}`,
}),
}
return (
<main className="main">
<div className="main__btn-container">
<button
style={overviewActive ? styles.buttonMobile(btnMobileStyle) : {}}
onClick={updateOverview}
className="main__btn"
>
Overview
</button>
<button
style={structureActive ? styles.buttonMobile(btnMobileStyle) : {}}
onClick={updateStructure}
className="main__btn"
>
Structure
</button>
<button
style={surfaceActive ? styles.buttonMobile(btnMobileStyle) : {}}
onClick={updateSurface}
className="main__btn"
>
Surface
</button>
</div>
I was able to solve the issue using this code:
const btnMobileStyle = useMediaQuery('(max-width: 768px)')
const styles = {
btnMobile: (btnMobileStyle) => ({
borderBottom: btnMobileStyle ? `2px solid ${themeColor}` : {},
backgroundColor: btnMobileStyle ? 'transparent' : themeColor,
}),
}
return (
<main className="main">
<div className="main__btn-container">
<button
style={overviewActive ? styles.btnMobile(btnMobileStyle) : {}}
onClick={updateOverview}
className="main__btn"
>
Overview
</button>
<button
style={structureActive ? styles.btnMobile(btnMobileStyle) : {}}
onClick={updateStructure}
className="main__btn"
>
Structure
</button>
<button
style={surfaceActive ? styles.btnMobile(btnMobileStyle) : {}}
onClick={updateSurface}
className="main__btn"
>
Surface
</button>
</div>
<div className="main__img-container">
<img
className="main__planet-img"
src={state.planetImg}
alt={state.planetName}
/>
<img
className={
surfaceActive
? 'main__planet-overlay main__planet-overlay--active'
: 'main__planet-overlay'
}
src={state.planetImgOverlay}
alt=""
/>
</div>

React can't perform state update on unmounted component - checking isMounted not fixing

I am using axios to return data from an API and trying to present this in to various nested components in my React App.
The code looks something like this:
const Building = () => {
const { bid } = useParams();
const { userAccessToken } = useAuth();
const [buildingData, setBuildingData] = useState([]);
const bearerToken = `Bearer ${userAccessToken}`;
React.useEffect(() => {
let isMounted = true;
const axiosConfig = {
headers: { Authorization: bearerToken },
};
axios
.get(
"http://localhost:3001/building?requestedlid=2&requestedbid=" + bid,
axiosConfig
)
.then(function (response) {
if (isMounted) {
setBuildingData(response.data[0]);
}
})
.catch(function (error) {
// handle error
console.log(error);
});
return () => {
isMounted = false;
};
}, [bearerToken, bid]);
return (
<React.Fragment>
<Helmet title="Building Profile" />
<Container fluid className="p-0">
<Breadcrumb className="float-end mt-2">
<Breadcrumb.Item href="/dashboard/default">Home</Breadcrumb.Item>
<Breadcrumb.Item href="/buildings/portfolio">
Portfolio
</Breadcrumb.Item>
<Breadcrumb.Item active>Building Profile</Breadcrumb.Item>
</Breadcrumb>
<h1 className="h3 mb-3">
Building Profile
<OffcanvasHelp
id="buildingprofile"
name="Building Profile"
scroll
backdrop
/>
</h1>
<div className="clearfix"></div>
<Row>
<Col xl="8">
<BuildingProfile
name={buildingData.building_name}
status={buildingData.status}
description={buildingData.description}
keycontacts={buildingData.key_contacts}
created={buildingData.stats.created_date}
golive={buildingData.stats.golive_date}
/>
<Rooms />
</Col>
<Col xl="4">
<AccountManager />
<Map location={buildingData.location} />
<GetSupport type="commercial" />
</Col>
</Row>
</Container>
</React.Fragment>
);
};
My problem is I am receiving the common error:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Using the methods described in many StackOverflow answers, to check an isMounted boolean as in my code above, I am unable to resolve the issue.
Weirdly, the issue specifically occurs when I am passing these two props to the BuildingProfile component:
created={buildingData.stats.created_date}
golive={buildingData.stats.golive_date}
If I don't pass these two props, everything else works fine.
I'm sure I'm missing something silly but after several hours of trying to figure it out I'm still stuck. Anybody who can provide a pointer or any tips, I would be really grateful.
Many thanks
--- Update - including the BuildingProfile component:
const BuildingProfile = ({
name,
status,
description,
created,
golive,
keycontacts,
}) => {
// Modal config for "Deactivate Building"
const initOpenModals = () => {
let modals = {};
colors.forEach((color, index) => {
modals = Object.assign({}, modals, { [index]: false });
});
console.log(modals);
return modals;
};
const [openModals, setOpenModals] = useState(() => initOpenModals());
const toggle = (index) => {
// Toggle selected element
setOpenModals((openModals) =>
Object.assign({}, openModals, { [index]: !openModals[index] })
);
};
const notyf = useContext(NotyfContext);
const [type] = useState("success");
const [duration] = useState("5000");
const [ripple] = useState(true);
const [dismissible] = useState(false);
const [positionX] = useState("right");
const [positionY] = useState("top");
const navigate = useNavigate();
return (
<Card>
<Card.Header className="mb-0 pb-0">
<Card.Title className="mb-0">
<IsAllowed to="edit:buildings">
<div className="card-actions float-end">
<Dropdown align="end">
<Dropdown.Toggle as="a" bsPrefix="-">
<MoreHorizontal />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item onClick={() => navigate("/buildings/edit")}>
Edit Building
</Dropdown.Item>
<React.Fragment key="deactivateBuilding">
<Dropdown.Item onClick={() => toggle("deactivateBuilding")}>
Deactivate Building
</Dropdown.Item>
<Modal
show={openModals["deactivateBuilding"]}
onHide={() => toggle("deactivateBuilding")}
centered
>
<Modal.Header closeButton>
<b>Admin Function:</b> Deactivate Building
</Modal.Header>
<Modal.Body className="m-3">
<p className="text-left mb-0">
Are you sure you want to deactivate the
<b>Bus Works</b> building? This will prevent the
building from showing up in the platform completely.
</p>
</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => toggle("deactivateBuilding")}
>
Close
</Button>{" "}
<Button
variant="danger"
onClick={() => {
toggle("deactivateBuilding");
notyf.open({
type,
message: "The building has been deactivated.",
duration,
ripple,
dismissible,
position: {
x: positionX,
y: positionY,
},
});
}}
>
Deactivate Building
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
</Dropdown.Menu>
</Dropdown>
</div>
</IsAllowed>
<h1 className="mb-0 pb-0">{name}</h1>
<Badge
className={
status === "Live Building"
? "my-2 btn-gradient inline"
: "my-2 inline"
}
bg="success"
>
{status}
</Badge>
</Card.Title>
</Card.Header>
<Card.Body>
<h5>Building Overview:</h5>
<p className="mb-4">{description}</p>
<div className="row">
<div className="col-md-4">
<div className="mb-4">
<h5>Created Date</h5>
<p>{created}</p>
</div>
</div>
<div className="col-md-4">
<div className="mb-4">
<h5>Go-Live Date</h5>
<p>{golive}</p>
</div>
</div>
<div className="col-md-4">
<div className="mb-4">
<h5>Key Contacts</h5>
<div>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar3}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar2}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<span className="me-1">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-right">Joe Bloggs</Tooltip>
}
>
<img
src={avatar1}
width="28"
height="28"
className="rounded-circle me-2"
alt="Joe Bloggs"
/>
</OverlayTrigger>
</span>
<Link to="#" className="d-inline-block text-muted fw-bold ms-2">
+2 more
</Link>
</div>
</div>
</div>
</div>
</Card.Body>
</Card>
);
};
I figured this out and wanted to share my answer in-case it helps anyone else.
It turns out that because I am trying to access the nested object property in the rendered component before the API call has finished, the property ("stats") doesn't exist. When the state eventually updates once the API call has finished, it cannot update the component resulting in the error I was seeing.
The way to fix this is to do something like this:
.... // useState should be set to an object, not an array
const [buildingData, setBuildingData] = useState({});
.... // skipping past intermediary code for brevity
.then(function (response) {
if (isMounted) {
setBuildingData({
name: response.data[0].building_name,
status: response.data[0].status,
description: response.data[0].description,
keycontacts: response.data[0].key_contacts,
created: response.data[0].stats.created_date,
golive: response.data[0].stats.golive_date
});
}
})
.... // then to access it in the component use:
<BuildingProfile
name={buildingData.name}
status={buildingData.status}
description={buildingData.description}
keycontacts={buildingData.keycontacts}
created={buildingData.created}
golive={buildingData.golive}
/>
Doing it this way means that the nested object is updated with API data inside of the useEffect hook, not in the component itself.
Hope this helps somebody.

How can I add item on localStroge through ReactJS

I don't understand how I can add an item on localStroge with the handle change method in react js.
The problem is I want to add a favorite list. When I click the favorite list all checkbox fill I need to control them.
I want to store the filled item in my local storage. handlechange function fill all favorite icon why?
Every click will be a single item fill. Is it possible to Material UI or Martial UI checkBox icon? How can I handle it?
Here is my UI view
function Main() {
// Here is my state define
const [checked, setChecked] = React.useState(
localStorage.getItem("love") === "true"
);
const handleChange = (e) => {
localStorage.setItem("love", `${e.target.checked}`);
setChecked(e.target.checked);
console.log(e.target.checked);
};
return (
<>
<div className="row mt-5">
{isLoading ? (
<>
{Array.from({ length }).map((_, i) => (
<MainLoading key={i} />
))}
</>
) : error ? (
<p>Error occured</p>
) : (
<>
{data?.map((product) => (
<div className="col-md-3 mb-5 text-center" key={product.id}>
<img
className="w-100"
style={{ height: "200px", objectFit: "contain" }}
src={product.image}
alt=""
/>
<div>{product.title.substring(0, 20)}</div>
<button
onClick={() => handelAddTocard(product)}
className="mt-3"
>
Add to card
</button>
<button>
<Link
to={`/details/${product.id}`}
className="mt-3 text-decoration-none text-black"
>
view details
</Link>
</button>
{/* How can i control evey single item */}
<Checkbox
checked={checked}
onChange={handleChange}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>
</div>
))}
</>
)}
</div>
</>
);
}
export default Main;
The problem is that you are using a boolean and you have no way to identify a specific item.
If you want to favorite multiple items, I would use something like this:
const [checked, setChecked] = React.useState(
JSON.parse(localStorage.getItem("loveIds") || "[]")
);
const handleCheck = (id, productChecked) => {
const newItems = productChecked ? [...checked, id] : checked.filter(x => x !== id);
localStorage.setItem("loveIds", JSON.stringify(newItemS));
setChecked(newItems);
console.log(newItems);
};
// ...
<Checkbox
checked={checked}
onChange={(e) => handleCheck(product.id, e.target.checked)}
icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
/>

Passing click function from One component to other component

In React material ui i am having two components where i am calling save function on button click, is it right way or not can anyone suggest the better way:
const callback = {};
return (
<>
{!state?.creditCard?.isSaved ? (
<Paper elevation={4} className={classes.paymentContainer}>
<Box className={classes.subPaymentContainer}>
<Typography className={classes.title}>Card Payment</Typography>
<CardPaymentForm
callback={callback}
validationPassed={() => actionsCollection.booking.saveCard(true, state.creditCard.lastFourDigits)}
formType="profileForm"
/>
<div>
<Button
type="submit"
onClick={(e) => callback.saveCard(e)}
value="SAVE CREDIT CARD"
className={classes.button}
/>
<div style={{ display: "flex", marginTop: 20 }}>
<img className={classes.lockIcon} src={lockIconInfo} alt="" />
<Typography className={classes.paymentInfo}>
<Link href="/terms" target={"_blank"}>
Terms of Payment
</Link>
.
</Typography>
</div>
</div>
</Box>
</Paper>
) : (
<div style={{ height: 373 }}>
<CardStored removeCard={removeCard} />
</div>
)}
</>
);
in CardPayementForm below calling the save function below is the code:
const CardPaymentForm = ({ classes, callback, validationPassed, formType, lastFourDigits }) {
useEffect(() => {
callback.saveCard = (e) => {
e.preventDefault();
=
if (validateForm()) {
=
validationPassed();
}
};
});
}
here without callback how to call save function directly in cardpaymentform, Any help please
I'm not sure this will apply to your problem but if you had a component_a
like
const ComponentA = ({handleClick}) => {
return(
<>
<button onClick(e => handleEvent(e))>
Click here
</button>
</>
}
and a component_b
const ComponentB = () => {
const handleClick = (e) => {
// do something with the event from component a
}
return(
<>
<ComponentA handleClick={handleClick}/>
</>
)
}

React: The onClick nested within a card is not called

I am using react hooks, and I have erased out other functions not associated with the function I am intending to call. The App functions are rendered
function App() {
const memoizedCallback = React.useCallback(() => {
console.log("Click happened");
}, []);
return (
<div className="App">
<ReactMapGl
{...viewport}
mapboxApiAccessToken={accesstoken}
onViewportChange={viewport => {
setviewport(viewport);
}}
>
{details.map(details => (
<Marker
key={details.name}
latitude={details.lat}
longitude={details.long}
>
<button
class="marker-btn"
onClick={e => {
e.preventDefault();
useselectedpark(details);
}}
>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
))}
{selectedpark ? (
<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>{selectedpark.postalcode}</Card.Text>
<div>
<Button variant="primary" onClick={memoizedCallback}>
Visit Gallery
</Button>
</div>
</Card.Body>
</Card>
</div>
</Popup>
) : null}
{console.log("in render", details)}
</ReactMapGl>
</div>
);
}
export default App;
the function I am intending to call is memoizedCallback. The function is called in an onClick of a button within a card.
The sequence of events is as such. A popup appears, and the user has an option to click on a button within the card that appears.
Problem: Currently, when the button is clicked right now, the function memoizedCallback is not called.
Why is that so, what did I miss here?

Resources