I have a list consisting of React components:
<div className="card-list">
{cards.map((card, i) => (
<Card card={card} key={card.cardId} index={i}/>
))}
</div>
The problem is that clicking on a card should open a modal window for the card that was clicked. To call the modal window I use the following code inside the Card component:
const Card = ({card, key, index}) => {
const dispatch = useDispatch();
const showModal = useSelector(state => state.modal.showContentModal);
return (
<div>
//this is card view
<div onClick={() => dispatch(showContentModal())}>
<h3>{card.name}</h3>
</div>
//this is modal window
<div id={`modal-overlay-container-${key}`} className={`modal-overlay ${showModal && "active"}`}>
<div id={`modal-div-${key}`} className={`modal ${showModal && "active"}`}>
<p className="close-modal" onClick={() => dispatch(hideContentModal())}>
<svg viewBox="0 0 20 20">
<path fill="#8e54e9" d="..."/>
</svg>
</p>
<div className="modal-content">
<CardContent card={card} index={index}/>
</div>
</div>
</div>
</div>
)
}
export default Card;
The showСontentModal() action changes the Boolean flag, which makes the modal window active, and hideModalContent() does the opposite. But because all the components in the list are linked to a single action, clicking on any card opens a modal window for all of them. Is there any way to trigger an action only for a specific card?
I see two ways you could solve this.
Use local state inside card component instead of redux state
Use card id instead of boolean in state.modal.showContentModal
If you don't need state.modal.showContentModal in other components then I would prefer first option.
const [showModal, setShowModal] = useState(false)
const handleOpenModal = () => setShowModal(true)
<div onClick={handleOpenModal}>
<h3>{card.name}</h3>
</div>
Related
I am working on my blog and am trying to implement Sanity. I was able to get my posts to show up with the json object returned from query with useState
I am trying to populate my React-Modal with the correct contents based on the post I have clicked with its _id or some kind of key. I simplified the code so it wouldn't be too long:
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
function toggleModal() {
setIsOpen(!isOpen);
}
return (
<>
{posts.map((posts) => (
<div key={posts._id}>
<h3 className="title" onClick={toggleModal}>
{posts.title}
</h3>
<div">
<a>
<span onClick={toggleModal}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal}>
// Closes modal
<button className="close-modal" onClick={toggleModal}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{posts.title}</h3>
<p className="body">{posts.body}</p>
</div>
)
</>
)
}
Whenever I click on a certain post, it always toggles on the first object.
Click to see gif demo
Edit: I was able to get it to work based on the answer given
const [state, setState] = useState({ isOpen: false, postId: null });
const openModal = React.useCallback(
(_key) => () => {
setState({ isOpen: true, postId: _key });
},
[]
);
function closeModal() {
setState({ isOpen: false, postId: null });
}
And with Modal tag I added
key={post.id == state.postId}
Now every divs and tags that renders the correct content.
However, I'm facing a slight issue. If I click on post[2] and it renders out post[0] content and in a blink of an eye changes to the correct content. Then when I click on post1, it renders and post[2] content and changes to the correct one. It keeps rendering the previous post. It's all in a blink of an eye, but still visible.
I can suggest using react hooks to solve your problem.
You can pass a function to useCallback's return, you can then call your function normally in the render by passing params to it.
See more: https://reactjs.org/docs/hooks-reference.html#usecallback
import * as React from 'react';
export default function News() {
// Json objects stored in posts
const [posts, setPosts] = useState([]);
// Used to toggle Modal on and off
const [isOpen, setIsOpen] = useState(false);
// React.useCallback.
const toggleModal = React.useCallback((id) => () => {
setIsOpen(!isOpen);
console.log(`Post id: ${id}`);
}, []);
return (
<>
{posts.map((post) => (
<div key={post._id}>
<h3 className="title" onClick={toggleModal(post._id)}>
{post.title}
</h3>
<div">
<a>
<span onClick={toggleModal(post._id)}>Read More</span>
</a>
</div>
// Clicking on either span or a tag shows the Modal
<Modal
isOpen={isOpen}
onRequestClose={toggleModal(post._id)}>
// Closes modal
<button className="close-modal" onClick={toggleModal(post._id)}>
<img
src="assets/img/svg/cancel.svg"
alt="close icon"/>
</button>
// Want to show content based on _id
<h3 className="title">{post.title}</h3>
<p className="body">{post.body}</p>
</div>
)
</>
)
}
I have a modal when opened, display auth user data, currently, I can only open the modal on the dashboard, but I want to be able to render it from anywhere in my application. How do I achieve this?
Dashboard
const [visible, setVisible] = useState(false)
const trigerModal = ()=>(
<ModalCustom visible={visible} setVisible={setVisible}>
<form>
<>
<h3>Select an Account</h3>
<ul className="account">
{accounts && accounts.map((item, i) => (
<li key={item.id}>
<h3>{item.name}</h3>
<h3>{item.email}</h3>
<h3> {item.phone}</h3>
</li>
))}
</ul>
<br />
</>
</form>
</ModalCustom>
)
return(
<div>
{trigerModal()}
<button onClick={()=> setVisible(true)}>Open modal</button
</div>
)
Profile
how do trigger the modal from this component
Two statements will answer virtually every react question:
Don't mutate state (not applicable here)
Lift state up (this is the answer to your question).
Create a context - wrap your application in it, and have any component useContext to open a modal with whatever components you want it in:
export const ModalContext = React.createContext();
const ModalProvider = ({children}) => {
const [modalContent,setModalContent] = useState();
return (
<ModalContext.Provider value={
useMemo(() => ({
hide:() => setModalContent(),
open:setModalContent
}),[]
}>
{modalContent ? <Modal>{modalContent}</Modal> : null}
{children}
</ModalContext.Provider>
)
}
Wrap you application in the ModalProvider component so the context will be available to all your components:
const AdminDashboard = () => (
<ModalProvider>
<OtherComponents/>
</ModalProvider>
)
SomeLink, a component that is anywhere inside AdminDashboard can use React.useContext to access the state in ModalProvider
const SomeLink = () => {
const { show } = React.useContext(ModalContext);
return (
<button onClick={() => show(<SomeModalContent/>)}>Click to Open!</button>
)
}
If you want to access it from anywhere You need to use Global State (like Redux or Mobx)
If you want to control this from parent component you can use useRef
I'm super new to React.js. I'm making changeable layouts using React.js. so i tried to use useState for rendering specific layout that I should click. so I tried to add setState for changing false in a function and made one another setState in the same function. but Too many re-renders Error came out. so what can i use for making changeable layout??
this is my code
import React, { useState } from "react";
import Panel from "./Panel";
import PanelTwo from "./PanelTwo";
import styled from "styled-components";
export default function Layout() {
const [showPanel, setShowPanel] = useState(false);
const [showPanel1, setShowPanel1] = useState(false);
const handleOnClick = () => setShowPanel(true);
const handleOnClick1 = () => setShowPanel1(true);
return (
<div>
<Main>
<div onClick={handleOnClick}>
<h1>Panel (1+3)</h1>
</div>
<div onClick={handleOnClick1}>
<h1>Panel (2+2) </h1>
</div>
<div>
<h1>Panel (2+3)</h1>
</div>
<div>
<h1>Panel (2+4)</h1>
</div>
<div>
<h1>Panel (3+1)</h1>
</div>
<div>
<h1>Panel (3+2)</h1>
</div>
</Main>
{showPanel ? <Panel /> : null}
{showPanel1 ? <PanelTwo /> : null}
</div>
);
}
and if i do this, it looks like this when i click two buttons
enter image description here
so i tried this .
const [showPanel, setShowPanel] = useState(false);
const [showPanel1, setShowPanel1] = useState(false);
const handleOnClick = () => setShowPanel(true);
const handleOnClick1 = () => setShowPanel(false);
setShowPanel1(true);
and i got Too many re-renders Error.
this is what i want to do
enter image description here
when i click each buttons then the exact only one layout is gonna show up .
but the problem is that it's overlapped when i click two buttons
like this
enter image description here
I'm sorry if my explanation is not good.
It seems like you really just want to toggle between the two layouts. You can do this with a single state value, callback, and conditional render via ternary.
export default function Layout() {
const [showPanel, setShowPanel] = useState(false);
const handleOnClick = () => setShowPanel(show => !show);
return (
<div>
<Main>
<div onClick={handleOnClick}>
<h1>Panel (1+3)</h1>
</div>
<div onClick={handleOnClick}>
<h1>Panel (2+2) </h1>
</div>
...
</Main>
{showPanel ? <Panel /> : <PanelTwo />}
</div>
);
}
OFC, this assumes you want to always show at least one of the layouts. If you want to start with both initially hidden (i.e. false) then you can toggle the other panel state false in the handlers to do that.
export default function Layout() {
const [showPanel, setShowPanel] = useState(false);
const [showPanel1, setShowPanel1] = useState(false);
const handleOnClick = () => {
setShowPanel(true);
setShowPanel1(false);
};
const handleOnClick1 = () => {
setShowPanel(false);
setShowPanel1(true);
};
return (
<div>
<Main>
<div onClick={handleOnClick}>
<h1>Panel (1+3)</h1>
</div>
<div onClick={handleOnClick1}>
<h1>Panel (2+2) </h1>
</div>
...
</Main>
{showPanel && <Panel />}
{showPanel1 && <PanelTwo />}
</div>
);
}
Right now i am in Home.js page and i want to render Article.js component/page when user click on particular card (Card.js component). Here is my Home.js code
const Home = () => {
const posts = useSelector((state) => state.posts)
const [currentId, setCurrentId] = useState(null)
const handleClick = () => {
return <Article />
}
return (
<div className="container">
<h4 className="page-heading">LATEST</h4>
<div className="card-container">
{
posts.map(post => <Card key={post._id} post={post} setCurrentId={setCurrentId} onClick={handleClick} />)
}
</div>
</div>
)
}
ONE MORE PROBLEM :
How can I send post variable into onClick method? when i send it method is getting called.
Thank You in Advance :)
It sounds like you want to use the React Router? As I take it you want to load the post as its own page?
I should also point out that any function passed to onClick cannot return anything. The only purpose return can serve in an event function is to exit the function early.
I do agree with #Jackson that you might want to to look into React Router. But you don't need it. You can conditionally render the Article component based on the currentId.
A click handler shouldn't return anything. Instead of returning the <Article /> from the onClick callback, you would use onClick to control the currentId state. You can pass a function that sets the currentId to the post id based on the post variable in your map like this: onClick={() => setCurrentId(post._id)}.
The return for your Home component will either render the list of posts or a current post, depending on whether or not you have a currentId or just null.
const Home = () => {
const posts = useSelector((state) => state.posts);
const [currentId, setCurrentId] = useState(null);
return (
<div className="container">
{currentId === null ? (
// content for list of posts - when currentId is null
<>
<h4 className="page-heading">LATEST</h4>
<div className="card-container">
{posts.map((post) => (
<Card
key={post._id}
post={post}
// arrow function takes no arguments but calls `setCurrentId` with this post's id
onClick={() => setCurrentId(post._id)}
/>
))}
</div>
</>
) : (
// content for a single post - when currentId has a value
<>
<div
// setting currentId to null exits the aritcle view
onClick={() => setCurrentId(null)}
>
Back
</div>
<Article
// could pass the whole post
post={posts.find((post) => post._id === currentId)}
// or could just pass the id and `useSelector` in the Article component to select the post from redux
id={currentId}
// can pass a close callback to the component so it can implement its own Back button
onClickBack={() => setCurrentId(null)}
/>
</>
)}
</div>
);
};
To pass in the click hadler the params you want, one could do something like this:
posts.map(post =>
<Card
key={post._id}
post={post}
onClick={() => handleClick(post)} />
)
i am working with negomi/react-burger-menu. i want to close my sidebar menu when a link is click not outside or cross button click just link click then my sidebar menu automatically close itself. But problem is my link is under another component , Suppose my component <ShipForMe/> and my link is under <ShipForMe/> like <NavLink to="/dashboard/ship-for-me/my-request/pending">My Request</NavLink>
Sidebar.js
<div className="sidebar-wrap " id="outer-container">
<div className="dashboard-menu rounded">
<ShipForMe/>
<MyWallet/>
<Profiles/>
<div className="sidebar-item ds-item">
<div className="sidebar-item__title">
<NavLink to="">VIP Center</NavLink>
</div>
</div>
</div>
<div className="dashboard-responsive rounded">
<Menu pageWrapId={ "page-wrap" } outerContainerId={ "outer-container" } isOpen={false}>
<div id="page-wrap" style={{marginTop: '-25%'}}>
<ShipForMe handleUrl={handleUrl}/>
<MyWallet/>
<Profiles/>
<div className="sidebar-item ds-item">
<div className="sidebar-item__title">
<NavLink to="">VIP Center</NavLink>
</div>
</div>
</div>
</Menu>
</div>
</div>
ShipForMe.js
<div className="ship-wrap ds-item">
<div className="sidebar-item">
<div className="sidebar-item__title">
Ship for me
</div>
<ul className="sidebar-item__lists">
<li>
<NavLink to="/dashboard/ship-for-me/my-request/pending">My Request</NavLink>
</li>
<li>
<NavLink to="/dashboard/ship-for-me/forwarding-parcel/abroad-to-bd">My Forwarding Parcel</NavLink>
</li>
</ul>
</div>
</div>
Note: isOpen{flase} is not working, it is only working when link is physical there.
First stack overflow response so, please bear with me.
Also, seems like I'm a bit late but hopefully this can help others.
In your Menu component, add: (1) onOpen={handleOpen}, (2) onClose={handleOpen}, and (3) isOpen={isOpen} properties.
onOpen will trigger when the sidebar nav menu is opened and we want
it to set isOpen to true
onClose will trigger whenever the user clicks on the cross icon, overlay, or escape key. (Assuming none of these have been disabled/modified)
isOpen controls whether the sidebar nav menu is rendered open (true) or close (false).
In your ShipForMe component add a reference to a function that will set isOpen to false. In my example ShipForMe is SideBarLinks. And the function is closeSideBar
Here is my example:
const [isOpen, setOpen] = useState(false)
const handleIsOpen = () => {
setOpen(!isOpen)
}
const closeSideBar = () => {
setOpen(false)
}
<Menu
isOpen={isOpen}
onOpen={handleIsOpen}
onClose={handleIsOpen}
>
<SideBarLinks closeSideBar={closeSideBar} />
</Menu
const SideBarLinks = ({ closeSideBar }) => {
return (
<>
<NavLink to="/#about" onClick={closeSideBar}>
About
</NavLink>
</>
Please do let me know if this helps and any feedback is appreciated.
Additional info: react version 17.0.1, react-burger-menu version 3.0.6
References: https://www.npmjs.com/package/react-burger-menu
Have spent a lot of time fiddling with problem. One critical part of the solution is to pass the appropriate callback function as a prop to the < Menu > component:
onStateChange={handleStateChange}
where handleStateChange is defined as:
const handleStateChange = (state) => {
setMenuOpen(state.isOpen);
};
where setMenuOpen is state setter as defined in:
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
you also need a handleCloseMenu function that calls your setter:
const handleCloseMenu = () => {
setIsMenuOpen(false);
};
you can then pass handleCloseMenu to an onclick of a button in your sidebar / burger menu.
all together you have:
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const handleCloseMenu = () => {
setIsMenuOpen(false);
};
const handleStateChange = (state) => {
setIsMenuOpen(state.isOpen);
};
return (
<Menu isOpen={isMenuOpen} onStateChange={handleStateChange}>
... Link 1
<button onClick = {()=>handleCloseMenu}/>
....
</Menu>
)
Here is a working sandbox example I found on the github repo:
And here is the github repo. There's an Issue posted about streamlining it further. But once you pass the correct callback, onStateChange={handleStateChange}, you should be good.