Modal only shows last element of mapped array in React - javascript

Right now im trying to render an elements respective contents if the user clicks on the button. As of right now since it is creating a modal for every element once i press a the button it opens up all of the modals, showing the last one.
I looked at previous questions regarding this and some assistance would be very appreciated.
For example, once i press on a button for that project card to view more about that project, it'll only show information for the last element of the array that is being mapped.
const [modalIsOpen, setModalIsOpen] = useState(false)
return (
<>
<Grid container
direction="row"
className="Project--Items"
xs={9} >
{projects.map((project) => (
<div key={project.id}className="Project--Card">
<img src={project.image} className="Item--Main--Image"/>
<div className="Card--Text">
<p>{project.name}</p>
<p>{project.description}</p>
</div>
<button onClick={() => setModalIsOpen(true)}>Open Modal</button>
<Modal isOpen={modalIsOpen} onRequestClose={()=> setModalIsOpen(false)}>
<h2>{project.name}</h2>
<p>{project.description}</p>
<div>
<button onClick={() => setModalIsOpen(false)}>Close Modal</button>
</div>
</Modal>
</div>
))}
</Grid>
</>
);
}

const [modalIsOpen, setModalIsOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState(null);
const expandModal = (project) => {
setSelectedProject(project);
setModalIsOpen(true);
}
const closeModal = () => {
setSelectedProject(null);
setModalIsOpen(true);
}
return (
<Grid container
direction="row"
className="Project--Items"
xs={9} >
{projects.map((project) => (
<div key={project.id}className="Project--Card">
<img src={project.image} className="Item--Main--Image"/>
<div className="Card--Text">
<p>{project.name}</p>
<p>{project.description}</p>
</div>
<button onClick={() => expandModal(project)}>OpenModal</button>
</div>
))}
<Modal isOpen={modalIsOpen} onRequestClose={closeModal}>
<h2>{selectedProject && selectedProject.name}</h2>
<p>{selectedProject && selectedProject.description}</p>
<div>
<button onClick={closeModal}>Close Modal</button>
</div>
</Modal>
</Grid>
);
}

The structure is a bit shaky here. You should have a hidden Modale above all your content, and simply pass it some information. It will avoid rendering countless modals on the dom.
What about something like:
function Modale ({isOpen, onClose, project}){
return isOpen && (
<>
<h2>{project.name}</h2>
<p>{project.description}</p>
<div>
<button onClick={onClose}>Close Modal</button>
</div>
</>
}
function List (){
const [modalIsOpen, setModalIsOpen] = useState(false)
const [focusProject, setFocusProject] = useState(null)
const onOpenModale = (project) => {
setModalIsOpen(true)
setFocusProject(project)
}
const onCloseModale = () => {
setModalIsOpen(false)
setFocusProject(null)
}
return (
<>
<Modale isOpen={modalIsOpen} onClose={onCloseModale} project={focusProject}/>
<Grid container direction="row" className="Project--Items" xs={9} >
{projects.map((project) => (
<div key={project.id} className="Project--Card">
<img src={project.image} className="Item--Main--Image"/>
<div className="Card--Text">
<p>{project.name}</p>
<p>{project.description}</p>
</div>
<button onClick={()=> onOpenModale(project)}>Open Modal</button>
</div>
))}
</Grid>
</>
);
}

Related

React Modal returns last value of mapped data

Struggling with this issue with the earlier answers not utilizing map function / functional components. When I click my Card, the modal only shows the data of the last Modal:
export const ModalCard = (props) => {
const productData = props.data;
const [modal, setModal] = React.useState(false);
const toggle = () => setModal(!modal);
return (
<Row>
{productData.map((v, i) => (
<Col className="py-4 btn" key={i} xs={12} md={4} lg={2}>
<div className="pb-4" onClick={toggle}>
<div className="product_card_padding">
<div className="pt-4">
<span className="card_product_subtitle">{v.headline}</span>
</div>
</div>
<Modal isOpen={modal}
toggle={toggle}
centered
>
<ModalBody className="product_modal" onClick={toggle}>
<div className="row pt-3 bg_white">
<Col>
<div>
<span className="card_product_subtitle">{v.headline}</span>
</div>
</Col>
</div>
</ModalBody>
</Modal>
</div>
</Col>
))}
</Row>
);
}
According to your code, multiple modals will be opened and you will see the last modal.
If you have 10 products, then 10 modals will be opened.
My suggestion is that you need to define a global modal outside map function and you need to define a new state variable to represent the selected product to be rendered on the modal.
selectedInd holds the data index to be rendered on modal.
const [selectedInd, setSelectedInd] = React.useState(-1);
Then toggle function would be changed to set -1 to hide modal.
const toggle = () => setSelectedInd(-1);
And move the modal outside map.
Try to use the following code pattern.
export const ModalCard = (props) => {
const productData = props.data;
const [selectedInd, setSelectedInd] = React.useState(-1);
const toggle = () => setSelectedInd(-1);
const modal = selectedInd >= 0 && (productData && productData.length > selectedInd);
return (
<React.Fragment>
<Row>
{productData.map((v, i) => (
<Col className="py-4 btn" key={i} xs={12} md={4} lg={2}>
<div className="pb-4" onClick={()=>setSelectedInd(i)}>
<div className="product_card_padding">
<div className="pt-4">
<span className="card_product_subtitle">{v.headline}</span>
</div>
</div>
</div>
</Col>
))}
</Row>
{modal && <Modal isOpen={modal} toggle={toggle} centered>
<ModalBody className="product_modal" onClick={toggle}>
<div className="row pt-3 bg_white">
<Col>
<div>
<span className="card_product_subtitle">{productData[selectedInd].headline}</span>
</div>
</Col>
</div>
</ModalBody>
</Modal>}
</React.Fragment>
);
}

Using Reactstrap: How to toggle only one Collapse at a time?

I am using Reactstrap to open and collapse multiple cards. Once opened, they stay open, and since I plan to use more of them (for articles), this will be a mess. Once I click on a button and open a card, I would like the others to close, so only one card is displayed at a time. How can I achieve this?
const [isOpenInfo, setIsOpenInfo] = useState(false);
const toggleInfo = () => setIsOpenInfo(!isOpenInfo);
const [isOpenArticle1, setIsOpenArticle1] = useState(false);
const toggleArticle1 = () => setIsOpenArticle1(!isOpenArticle1);
const [isOpenArticle2, setIsOpenArticle2] = useState(false);
const toggleArticle2 = () => setIsOpenArticle2(!isOpenArticle2);
In my menu, I have a button "More Info", when clicked, it opens a list of collapsed articles and when clicking on each title, it opens the article (but I just want one article to open at a time). So it's like a collapse inside a collapse...
<Button className="info-button" color="primary" onClick={toggleInfo}>
More Info
</Button>
<Collapse isOpen={isOpenInfo}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div className="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={toggleArticle2}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={toggleArticle1}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={isOpenArticle2}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={isOpenArticle1}>
<Card className="card">
<CardBody className="card-body">
<Article1 />
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
You can create a default variable consisting all your articles and use it to set the state. Create a single state variable for all of your collapsible.
const DEFAULT_ARTICLES = {
article1: false,
article2: false,
};
const [articles, setArticles] = useState(DEFAULT_ARTICLES);
const toggleArticle = (key) => setArticles({
...DEFAULT_ARTICLES,
[key]: true,
});
And on your render function use the key to open the collapse and toggle the collapse.
<Collapse isOpen={isOpenInfo}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div class="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={() => toggleArticle('article2')}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={() => toggleArticle('article1')}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={articles['article1']}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={articles['article2']}>
<Card className="card">
<CardBody className="card-body">
<Article1 />
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
You can use one state to control all the collapses.
const [openedCollapse, setOpenedCollapse] = useState("");
const openCollapse = e => { // this is the button onClick handler
setOpenedCollapse(e.target.dataset.collapse);
};
Then your jsx looks like this:
<Button
className="article1-button"
color="primary"
data-collapse="article1" // A dataset param
onClick={openCollapse}>
<h3>Article 1</h3>
</Button>
<Collapse isOpen={openedCollapse === "article1"}>
<Card className="card">
<CardBody className="card-body">
<Article2 />
</CardBody>
</Card>
</Collapse>
dataset info
You can use an object as state with a callback function when toggling the More Info collapse and then utilize a simple string to determine which article should be opened when the main Collapse is open and a Button was clicked inside of it.
For example, updating whether or not the main collapse is open:
const toggleMoreInfo = () => {
setState(prevState => {
// this gives us access to the current state when setState is executed
// then we can inverse a boolean when the More Info button is clicked
return {
article: "", // resets the open article
moreInfoOpen: !prevState.moreInfoOpen // false => true || true => false
}
})
}
For example, updating which article should be opened:
const handleArticleOpen = (article) => {
setState((prevState) =>
return {
// keep whatever is in state as is by spreading it out (in this case, "moreInfoOpen" stays unchanged)
...prevState, //
// and just override the article with a passed in string
article
}));
};
When dealing with coupled state, I like to use objects over individual states, since it's easier to keep both sets of state in sync. For a demo and the full code, look below...
Working demo:
Code
import * as React from "react";
import { Button, Card, CardBody, Collapse } from "reactstrap";
import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
export default function App() {
const [state, setState] = React.useState({
articleOpen: "",
moreInfoOpen: false
});
const { article, moreInfoOpen } = state;
const toggleMoreInfo = () => {
setState((prevState) => ({
article: "",
moreInfoOpen: !prevState.moreInfoOpen
}));
};
const handleArticleOpen = (article) => {
setState((prevState) => ({
...prevState,
article
}));
};
return (
<div className="app">
<Button className="info-button" color="primary" onClick={toggleMoreInfo}>
More Info
</Button>
<Collapse isOpen={moreInfoOpen}>
<Card className="card">
<CardBody className="card-body">
<div className="section section-articles">
<div className="articles-buttons">
<Button
className="article2-button"
color="primary"
onClick={() => handleArticleOpen("2")}
>
<h3>Article 2</h3>
</Button>
<Button
className="article1-button"
color="primary"
onClick={() => handleArticleOpen("1")}
>
<h3>Article 1</h3>
</Button>
</div>
<Collapse isOpen={article === "2"}>
<Card className="card">
<CardBody className="card-body">
<div>Article 2</div>
</CardBody>
</Card>
</Collapse>
<Collapse isOpen={article === "1"}>
<Card className="card">
<CardBody className="card-body">
<div>Article 1</div>
</CardBody>
</Card>
</Collapse>
</div>
</CardBody>
</Card>
</Collapse>
</div>
);
}

Modal dialog displays from all elements of mapped array. How to select each item by ts and react js?

This code:
How to display a dialog when a button is clicked using react and typescript?
I wanna open dialog from each todos, how to make it ? I used react js and typescript. Help me to resolve this problem.
interface ListProps {
todos: INote[];
onDelete: (title: string) => void;
}
const TodoList: React.FunctionComponent<ListProps> = ({ todos, onDelete }) => {
const [showAlert, setShowAlert] = useState(false);
const [todo, setTodos] = useState(null);
How to select each item by ts?It doesn't work. What is reason? Thanks!
const handleOpenDialog = (todos: any) => {
setTodos(todos);
setShowAlert(true);
};
const handleCloseDialog = () => {
setShowAlert(false);
};
return (
<>
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
</>
);
};
export default TodoList;
just add a condition to only show the AlertDialog on selected todos
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todos.title===todo?.title && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
or just move the AlertDialog outside the map
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
</div>
))}
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</section>

React trigger only one element in array

I am in the process of making a comment system like the one on youtube. In my implementation, when I click on modify, all comments are now inputs but only the value of the selected input will be modified. how to trigger only the element i clicked.
as you can see it triggers all the array elements
function App() {
const [open, setOpen] = useState(false);
return (
<div className="container mt-5">
<MDBRow>
{data &&
data.map((item) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
<button
onClick={() => {
setOpen(true);
}}
>
Modifier
</button>
</div>
</>
)}
{open && (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
)}
</MDBCol>
))}
</MDBRow>
</div>
);
}
export const UpdateData = ({ name, id, onAbort, submit }) => {
const formik = useFormik({
initialValues: {
id: id,
name: name,
},
onSubmit: async (values) => {
console.log(values);
submit();
},
});
return (
<form onSubmit={formik.handleSubmit}>
<MDBInput
value={formik.values.name}
name="name"
onChange={formik.handleChange}
/>
<div className="float-right">
<span onClick={onAbort} className="text-capitalize grey-text">
Cancel
</span>
<button type="submit">confirm</button>
</div>
</form>
);
};
And this is the sandbox
that i have created
To trigger only one element to be clicked you have to pass the index
function App() {
const [open, setOpen] = useState(false);
const [selectedRow, setSelectedRow] = useState(undefined);
const onSelectedRow = (index) => {
setSelectedRow(index);
setOpen(true);
}
return (
<div className="container mt-5">
<MDBRow>
{data &&
// here you will get the index
data.map((item,index) => (
<MDBCol md="7" lg="7" key={item.id} className="mb-4">
{!open && (
<>
<div className="font-weight-bolder float-left pr-2">
{item.name}
</div>
<div className="float-right pr-2">
// Now onClick pass the index of selected row to onSelectedRow
<button
onClick={() =>onSelectedRow(index)}
>
Modifier
</button>
</div>
</>
)}
// here add condition to open selected row
{ (open === true && selectedRow === index) ? (
<UpdateData
id={item.id}
name={item.name}
onAbort={() => setOpen(false)}
submit={() => setOpen(false)}
/>
) : null
}
</MDBCol>
))}
</MDBRow>
</div>
);
}
Sandbox code https://codesandbox.io/s/youthful-wave-k4eih?file=/src/App.js
If you have any queries comment below!
instead of having a false default value in your hook you should have a unique key for each element. By default, it applies to all elements.

Trying to switch between modal components using React

So I have a start page that gives options to open a login modal or sign up modal. However, once you are in the login modal I give an option so you can switch to sign up modal. However, I can't seem to get this to work. The one time I got it to work, the modal showed up in the wrong section of the screen since it was being opened in relation to the login modal and not the start page.
I am new to React so any insight would be appreciated. Should I use redux, since I can't pass props from child to parent. So that way when I return to start page I can rerender with info saying that I had clicked sign-up link on the login modal.
function LoginContent(props) {
const [ open, setOpen ] = useState(false)
const { show, closeModal } = props;
function handleSubmit(e){
e.preventDefault();
}
function handleSignUpButton(){
closeModal();
console.log(open)
setOpen(!false)
console.log(open)
}
//added so that the component doesn't get affected by parent css
//and is being rendered from the "modal-root" DOM node from the index.html file
return ReactDOM.createPortal(
<>
<div className={show ? "overlay" : "hide"} onClick={closeModal} />
<div className={show ? "modal" : "hide"}>
<button onClick={closeModal} id="close">X</button>
<div className="login_form">
<h1> Log in to Continue </h1>
<form onSubmit={handleSubmit}>
<input className="username" type='text' name='username' placeholder='Email Address' />
<input className="password" type='password' name='password' placeholder='password' />
<button className="login_button"> Sign In</button>
</form>
</div>
<div className="login_demo">
<h3 className="login_demo_pointer" type="submit">Demo Login</h3>
</div>
<hr />
<div className="login_switch">Don't have an account.
<button className="signup_link" onClick={handleSignUpButton}>Sign Up</button>
{open && <SignUpContent open={open} closeModal={closeModal} show={show} />} </div>
</div>
</>, document.getElementById("modal-root")
);
}
function Start() {
const history = useHistory();
const [showLogin, setLogin ] = useState(false);
const openModalLogin = () => setLogin(true);
const closeModalLogin = () => setLogin(false);
const [showSignUp, setShow ] = useState(false);
const openModalSignUp = () => setShow(true);
const closeModalSignUp = () => setShow(false);
return (
<div className="bodyStart">
<img src="https://i.imgur.com/5gjRSmB.gif" alt="" id="bg" />
<div className="start_logo">
<img src={require("../styling/logo.png")} alt="" onClick={() => {
history.push('/home')
history.go(0)}} className="logo" />
</div>
<div className="start">
<div className="start_heading">
<h2>Mother Nature is Calling.</h2>
<h4>Find a place to recharge and escape the day to day.</h4>
</div>
<div className="start_location">
<p>Where?</p>
<div className="start_input">
<input type="text" placeholder="anywhere" />
<ArrowForwardIcon onClick={() => {
history.push('/search')
history.go(0)}}
className="arrow" fontSize="large"/>
</div>
</div>
<div className="start_authentication">
<Button className="login"
variant="contained"
color="primary"
size="large"
onClick={() => openModalLogin()}> Login </Button>
{showLogin && <LoginContent closeModal={closeModalLogin} show={showLogin} />}
<Button className="signup"
variant="contained"
size="large"
onClick={()=> openModalSignUp()}> Sign-Up </Button>
{showSignUp && <SignUpContent closeModal={closeModalSignUp} show={showSignUp} />}
</div>
</div>
</div>
)
}
I have made similar modals with Material-UI. You can change loginOpen state and signupOpen states in modals. See codepen below
Codepen
const { useState } = React;
const { Button, Dialog, DialogTitle, DialogContent, DialogActions } = MaterialUI;
function LoginDialog(props) {
const { open, setLoginOpen, setSignupOpen } = props;
const switchSignup = (event) => {
setLoginOpen(false)
setSignupOpen(true)
}
return (
<Dialog aria-labelledby="simple-dialog-title" open={open}>
<DialogTitle id="simple-dialog-title">LOGIN</DialogTitle>
<DialogContent>If you don't have an account, press SIGNUP</DialogContent>
<DialogActions>
<Button onClick={(event) => {setLoginOpen(false)}}>CLOSE</Button>
<Button>LOGIN</Button>
<Button onClick={switchSignup}>SIGNUP</Button>
</DialogActions>
</Dialog>
);
}
function SignupDialog(props) {
const { open, setLoginOpen, setSignupOpen } = props;
const switchLogin = (event) => {
setSignupOpen(false)
setLoginOpen(true)
}
return (
<Dialog aria-labelledby="simple-dialog-title" open={open}>
<DialogTitle id="simple-dialog-title">SIGNUP</DialogTitle>
<DialogContent>If you have an account, press LOGIN</DialogContent>
<DialogActions>
<Button onClick={(event) => {setSignupOpen(false)}}>CLOSE</Button>
<Button>SIGNUP</Button>
<Button onClick={switchLogin}>LOGIN</Button>
</DialogActions>
</Dialog>
);
}
const App = () => {
const [loginOpen, setLoginOpen] = useState(false)
const [signupOpen, setSignupOpen] = useState(false)
const handleLogin = (event) => {
setLoginOpen(true)
}
const handleSignup = (event) => {
setSignupOpen(true)
}
return (
<div>
<Button variant='contained' color='primary' onClick={handleLogin} >
LOGIN
</Button>
<Button variant='outlined' color='primary' onClick={handleSignup} >
SIGNUP
</Button>
<LoginDialog open={loginOpen} setLoginOpen={setLoginOpen} setSignupOpen={setSignupOpen} />
<SignupDialog open={signupOpen} setLoginOpen={setLoginOpen} setSignupOpen={setSignupOpen} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));

Categories

Resources