How to make a dynamic modal in React? - javascript

I would like to open a modal when I click on a card, and this modal contains more informations about the card content that was clicked.
Here in my example, the cards are replaced by buttons.
So here how to change the modal content depending on the button clicked ?
I saw this post : Change react-modal data dynamically. But I don't understand his solution.
App.js :
function App() {
const [modal, setModal] = useState({ show: false, data: null });
const openNoWebsite = () => {
setModal({ show: true, data: { title: 'No website in sight', subtitle: 'Having a website in 2021 is essential.' } });
};
const openMoreMoney = () => {
setModal({ show: true, data: { title: 'More money', subtitle: "You think you have an awesome product, but people don't really notice it and your sales numbers are not going up."} });
const handleCloseModal = () => {
setModal({ show: false, data: null });
};
return {
<div className="main">
<button className="solidText" onClick={openNoWebsite}>
Button 1
</button>
<button className="solidText" onClick={openMoreMoney}>
Button 2
</button>
{modal.show && modal.data && <Modal closeModal={handleCloseModal} />}
</div>
}
}
Modal.jsx :
function Modal({ closeModal, data }) {
return (
<div className="modal-container">
<div className="modal">
<button className="btn btn--close" onClick={() => closeModal()}>X</button>
<div className="modal__body">
<h1 className="modal__title">{data.title}</h1>
<h2 className="modal__subtitle">{data.subtitle}</h2>
<ul>
<span>How I will help you :</span>
<li>Discover together your goals through a strategy session</li>
<li>Discuss about the possible solutions to implement</li>
<li>A glorious online presence a.k.a. website</li>
</ul>
</div>
<div className="modal__footer">
<p>Wanna put your business out there ?</p>
<button className="btn btn--main">LET'S TALK</button>
</div>
</div>
</div>
)
}

What you can do is to use a state that contain the data you want to show inside your Modal, usually what I do is that I create a state const [modal, setModal] = useState({ show:false, data: null }) and when I press a button I update data accordingly to what I want to show.
Here I used car caracteristic put you can put whatever you want (description, big text, etc...).
The state show is not really usefull but I prefer to use it for clarity (you can just check if !data to know if you want to open or not your modal)
Example
function Modal({ closeModal, data }) {
return (
<div className="modal-container">
<div className="modal">
<button className="btn btn--close" onClick={() => closeModal()}>
X
</button>
<div className="modal__body">
<h1 className="modal__title">
{data.name} {data.color}
</h1>
<h2 className="modal__subtitle">Having a website in 2021 is essential.</h2>
<ul>
<span>How I will help you :</span>
<li>Discover together your goals through a strategy session</li>
<li>Discuss about the possible solutions to implement</li>
<li>A glorious online presence a.k.a. website</li>
</ul>
</div>
<div className="modal__footer">
<p>Wanna put your business out there ?</p>
<button className="btn btn--main">{data.name}</button>
</div>
</div>
</div>
);
}
function App() {
const [modal, setModal] = useState({ show: false, data: null });
const openAudi = () => {
setModal({ show: true, data: { name: 'Audi', color: 'red' } });
};
const openBMW = () => {
setModal({ show: true, data: { name: 'BMW', color: 'blue' } });
};
const handleClose = () => {
setModal({ show: false, data: null });
};
return (
<div className="main">
<button className="solidText" onClick={openAudi}>
Button Audi
</button>
<button className="solidText" onClick={openBMW}>
Button BMW
</button>
{modal.show && modal.data && <Modal closeModal={handleClose} data={modal.data} />}
</div>
);
}

I found this solution, not sure if it's the best but it work.
const [modal, setModal] = useState({ show: false, requestedModalId: 0});
const handleOpenModal = (id) => {
setModal({ show: true, requestedModalId: id });
};
const handleCloseModal = () => {
setModal({ show: false });
};
return {
<div className="main">
<button onClick={() => handleOpenModal(1)}>Button 1</button>
<button onClick={() => handleOpenModal(2)}>Button 2</button>
{modal.show && modal.requestedModalId === 1 && <Modal closeModal={handleCloseModal}>
<div>Modal Child 1</div>
</Modal>}
{modal.show && modal.requestedModalId === 2 && <Modal closeModal={handleCloseModal}>
<div>Modal Child 2</div>
</Modal>}
</div>
}
function Modal(props) {
return (
<div className="modal-container">
<div className="modal">
<button className="btn btn--close" onClick={() => props.closeModal()}>X</button>
{props.children}
</div>
</div>
)
}

Related

How to add active class when clicking button React Js?

I have 3 buttons and i want to add a class active when i click one of them. If i click another button i want to add active to that button and remove it from the previous one. i tried some ways but failed. i'm new in react .
function Filters() {
const [isActive, setIsActive] = useState(false);
const dispatch = useDispatch();
const handleClick = (e) => {
if (e.target.value === "Hot") {
dispatch(fetchHotImages());
} else if (e.target.value === "Top") {
dispatch(fetchTopImages());
} else {
dispatch(fetchUserImages());
}
};
return (
<div className={styles.filtersContainer}>
<div className={styles.filtersLeft}>
<h1 className={styles.filtersTitle}>Explore Gallery</h1>
</div>
<div className={styles.filtersRight}>
<div className={styles.trending}>
<button className={styles.actions} onClick={handleClick}>
Hot
</button>
<button className={styles.actions} onClick={handleClick}>
Top
</button>
<button className={styles.actions} onClick={handleClick}>
User
</button>
</div>
</div>
</div>
);
}
To add the active class using the above code, I'm guessing you're looking to add the active class to the buttons. All you'd have to add a style ternary in this form and change the state:
function Filters() {
const [isActive, setIsActive] = useState({
hotActive: false,
topActive: false,
userActive: false,
});
const dispatch = useDispatch();
const handleClick = (value) => {
if (value === "Hot") {
dispatch(fetchHotImages());
setIsActive({hotActive: true, topActive: false, userActive: false})
} else if (value === "Top") {
setIsActive({hotActive: false, topActive: true, userActive: false})
dispatch(fetchTopImages());
} else {
setIsActive({hotActive: false, topActive: false, userActive: true})
dispatch(fetchUserImages());
}
};
return (
<div className={styles.filtersContainer}>
<div className={styles.filtersLeft}>
<h1 className={styles.filtersTitle}>Explore Gallery</h1>
</div>
<div className={styles.filtersRight}>
<div className={styles.trending}>
<button className={isActive.hotActive ? {styles.actions} : {}} onClick={()=>handleClick("Hot")}>
Hot
</button>
<button className={isActive.topActive ? {styles.actions} : {}} onClick={()=>handleClick("Top")}>
Top
</button>
<button className={isActive.userActive ? {styles.actions} : {}} onClick={()=>handleClick("User")}>
User
</button>
</div>
</div>
</div>
);
}
A better way would be to make use of CSS, but this should work for you.

Why is my useEffect hook updating too late?

I am trying to have an image and text update when I click a button, which it does, but the image is from the previous state. Is there a way to have it so that it changes instantly? Any help is appreciated. Using React btw.
function Footer({ spotify }) {
const [{ item, playing }, dispatch] = useDataLayerValue();
useEffect(() => {
spotify.getMyCurrentPlaybackState().then((r) => {
dispatch({
type: "SET_PLAYING",
playing: r.is_playing,
});
dispatch({
type: "SET_ITEM",
item: r.item,
});
});
}, [spotify]);
const handlePlayPause = () => {
if (playing) {
spotify.pause();
dispatch({
type: "SET_PLAYING",
playing: false,
});
} else {
spotify.play();
dispatch({
type: "SET_PLAYING",
playing: true,
});
}
};
return (
<div className='footer'>
<div className="footer__left">
<img className="footer__albumLogo"
src={item?.album.images[0].url}
alt={item?.name}
key={item?.album.images[0].url}/>
{item ? (
<div className="footer__songInfo">
<h4>{item.name}</h4>
<p>{item.artists.map((artist) => artist.name).join(", ")}</p>
</div>
) : (
<div className="footer__songInfo">
<h4>No song is playing</h4>
<p>...</p>
</div>
)}
</div>
// NEW EDIT
<div className="footer__center">
<ShuffleIcon className="footer__green" />
<SkipPreviousIcon className="footer__icon" />
{playing ? (
<PauseCircleOutlineIcon
onClick={handlePlayPause}
fontSize="large"
className="footer__icon"
/>
) : (
<PlayCircleOutlineIcon
onClick={handlePlayPause}
fontSize="large"
className="footer__icon"
/>
)}
<SkipNextIcon className="footer__icon" />
<RepeatIcon className="footer__green" />
</div>
</div>
)
}
export default Footer
I think that the issue is within the "div className="footer__left"" but it may be an entirely different issue.

Using useEffect like ComponentDidUpdate

I am trying to have 3 buttons where if one is in an active state, the other 2 will automatically be inactive.
if (isActive === "true") {
setActive2("false")
setActive3("false")
}
if (isActive2 === "true") {
setActive("false")
setActive3("false")
}
if (isActive3 === "true") {
setActive("false")
setActive2("false")
}
I'm aware there's probably a better way of doing this and this is a brute force option, and I'm open to your suggestions.
I have tried putting this block of code in a function and running it whenever the buttons are clicked, but that is giving me the previous state instead of the current state.
So I was suggested to use the useEffect hook.
useEffect(() => {
if (isActive === "true") {
setActive2("false")
setActive3("false")
}
if (isActive2 === "true") {
setActive("false")
setActive3("false")
}
if (isActive3 === "true") {
setActive("false")
setActive2("false")
}
}, [isActive, isActive2, isActive3]);
However this is giving me the same issue, where the previous state is being applied.
I am for sure doing something very wrong with this hook (i have never used it before).
I have a codesandbox with all my code here
Have modified only the onChange handler in an efficient way without touching JSX much and have worked on your set-up only. CodeSandBox Link Checkbox-Selection
Some Major changes that I did are as follows:
Instead of setting seperate state to each button, I have used a single object with 3 keys isActive, isActive2 and isActive3.
const [btnStatus, setBtnStatus] = useState({
isActive: true,
isActive2: true,
isActive3: true
});
Your Handler looks something like this now.
const addPizza = (e) => {
setPizzaSize(e.target.name);
setStartPrice(parseInt(e.target.value));
const currentActive = e.target.id;
if (currentActive === "isActive") {
setBtnStatus({ isActive: true, isActive2: false, isActive3: false });
console.log("1");
}
if (currentActive === "isActive2") {
setBtnStatus({ isActive: false, isActive2: true, isActive3: false });
console.log("2");
}
if (currentActive === "isActive3") {
setBtnStatus({ isActive: false, isActive2: false, isActive3: true });
console.log("3");
}
console.log(btnStatus);
};
In your JSX each button will look like this, with own ids to track the status of button.
<button
name="Extra Large"
className={
btnStatus.isActive3
? "button btn fourth"
: "button btn fourthActive"
}
value="20"
onClick={addPizza}
id="isActive3"
>
Extra large
</button>
And here you go. All working nicely with the same code :)
I have update the code a little bit, you can create seprate constants and use them to reduce the code and also, to keep the active state use a single state only.
https://codesandbox.io/s/gracious-franklin-m8wkx?file=/src/CYO.js:0-4147
import React, { useState, useEffect } from "react";
import ButtonClickable from "./button";
import ButtonClickable2 from "./button2";
import { burgerSize, vegToppings, nonvegToppings } from "./const/size";
import "./index.css";
const CYO = () => {
const [pizzaSize, setPizzaSize] = useState("Choose your Pizza Size");
const [activeSize, setActiveSize] = useState(burgerSize.MEDIUM);
const [toppings, setToppings] = useState([]);
const [startPrice, setStartPrice] = useState(0);
const addPizza = (e) => {
setPizzaSize(e.target.name);
setStartPrice(parseInt(e.target.value));
};
const CheckSize = () => {
if (pizzaSize === "Choose your Pizza Size") {
alert("You must choose a pizza size");
} else if (toppings.length === 0) {
alert("Are you sure you don't want toppings?");
} else {
alert("Sorry, this isn't a real pizza place.");
}
};
const ToppingPlusMinus = (e) => {
const { value } = e.target;
const position = toppings.indexOf(value);
if (position !== -1) {
return removeTopping(value);
}
return addTopping(value);
};
const removeTopping = (value) => {
// We need to filter out the value from the array and return the expected new value
setToppings(toppings.filter((topping) => topping !== value));
//handleToggle();
};
const addTopping = (value) => {
setToppings([...toppings, value]);
// handleToggle();
};
let toppingPrice = toppings.length * 1.5;
let price = startPrice + toppingPrice;
return (
<div className="container CYO">
<h2 className="text-center white">Create your own pizza</h2>
<div className="row">
<div className="col-sm-8">
<div className="">
<img
src="./pizza.png"
className="img-fluid pizza"
alt="Pizza"
></img>
</div>
<h3 className="white">{pizzaSize}</h3>
<p className="white">
Your Toppings: <br />
<div className="col-lg-12">
{toppings
.filter((x) => x.name !== "")
.map((toppings) => (
<img
src={toppings}
alt="topping"
width="100px"
height="100px"
></img>
))}
</div>{" "}
</p>
</div>
<div className="col-sm-4">
<h3 className="white">Pizza size</h3>
{Object.values(burgerSize).map((value) => (
<button
name={value}
className={
activeSize !== value
? "button btn fourth"
: "button btn fourthActive"
}
value="10"
onClick={(event) => {
addPizza(event);
setActiveSize(value);
}}
>
{value}
</button>
))}
<br />
<h3 className="white">Toppings</h3>
<p className="white">Toppings are $1.50 each</p>
<div className="topping-wrapper">
<h4 className="white">Meats</h4>
{nonvegToppings.map(({ name, image }) => (
<ButtonClickable
onClick={(event) => {
ToppingPlusMinus(event);
}}
name={name}
value={image}
/>
))}
<h4 className="white">Veggies</h4>
{vegToppings.map(({ name, image }) => (
<ButtonClickable2
onClick={(event) => {
ToppingPlusMinus(event);
}}
name={name}
value={image}
/>
))}
</div>
</div>
<div className="pricefooter">
<p className="price">Price: ${price}</p>
</div>
<div className="pricefooter2">
<button className="checkout button btn fourth" onClick={CheckSize}>
Checkout
</button>
</div>
</div>
</div>
);
};
export default CYO;

Render a modal when I click a button - React

I have this "trash" button:
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => this.props.handleProjectDelete(e, project.id)}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>
This is the page with the button.
And when I click it I want a component called CustomModal to render with this props:
<CustomModal
alternateModalClass='c-group-stickies-modal'
onClosePress={this.handleCloseClick}
alternateCloseButtonClass='c-group-stickies-modal__close-button'
text={'are you sure you want to delete it?'}
/>
So a modal like this can appear:
But I don't know how to do that.
This is the component that has the trash button:
https://jsfiddle.net/10u6pfjp/
And this is the CustomModal component: https://jsfiddle.net/cp29ms8g/
As others have mentioned, you should be approaching this with a conditional statement as to whether or not your modal should appear by having a variable in this.state. Change it in your button onClick. Since you now have 2 functions to run, I made a new function called handleProjectDelete in your component to handle both at once.
At the bottom of your render, you'll see that I added the conditional where you should place a modal. I used <Modal/> as a placeholder. Use CSS to force it into a position that's appropriate for a modal.
const MAX_PROJECTS_PER_PAGE = 10
export class ProjectsTable extends Component {
constructor() {
super()
this.state = {
showModal: false
}
}
componentWillReceiveProps(nextProps) {
const { history, organizations, hasFetched } = nextProps
if (!deepEqual(this.props, nextProps) && hasFetched) {
if (!organizations || organizations.isEmpty()) {
history.push('/beta-code')
}
}
}
handleProjectDelete(e, project.id) {
this.setState({showModal: true})
this.props.handleProjectDelete(e, project.id)
}
renderProjectsTable() {
const { projects } = this.props
const projectsWithoutDefault = projects.filter(proj => proj.name !== 'default')
const projectsTable = projectsWithoutDefault.map((project) => {
return ({
name: <NavLink to={`/projects/${project.id}`}> {project.name} </NavLink>,
team: <UsersList users={fromJS(project.users)} showBadge showMax={5} type='list' />,
retro:
(project.lastRetro)
? <NavLink className='c-nav-link'
exact to={`/retrospectives/${project.lastRetro.id}`}>
{moment.utc(project.lastRetro.date)
.format('dddd, DD MMMM YYYY').toString()}
</NavLink> : <div>No retros found</div>,
delete:
<div className='delete-align'>
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => this.handleProjectDelete(e, project.id)}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>
</div>
})
})
return projectsTable
}
render () {
return (
<div className='o-wrapper u-margin-top'>
<TablePagination
title='Projects'
data={ this.renderProjectsTable()}
headers={['Name', 'Team', 'Last Retrospective', ' ']}
columns='name.team.retro.delete'
nextPageText='>'
prePageText='<'
perPageItemCount={ MAX_PROJECTS_PER_PAGE }
totalCount={ this.renderProjectsTable().length }
arrayOption={ [['size', 'all', ' ']] }
/>
{ this.state.showModal ? <div className='delete-modal'><Modal/><div/> : null }
</div>
)
}
}
const mapStateToProps = ({
projects
}) => ({
hasFetched: projects.get('hasFetched'),
projects: projects.get('projects')
})
ProjectsTable.defaultProps = {
projects: []
}
export default connect(mapStateToProps)(ProjectsTable)
I hope you can do this as below
<button
type='reset'
className='c-btn--ghost no-border'
onClick={(e) => {
this.props.handleProjectDelete(e, project.id);
this.renderModal;
}}>
<i className='fa fa-trash u-margin-right-tiny'/>
</button>

Set state for only for self data into map on reactjs

I have a object's array of users and i'm using map to show them, each user have a option buttons that is 'edit' and 'remove' options each option have a onlclick function that set a state to show another view so the code explain itselft
class App extends React.Component {
state = {
edit: false,
remove: false
}
handleEdit = () => {
this.setState({ edit: true })
}
handleRemove = () => {
this.setState({ remove: true })
}
cancelEdit = () => {
this.setState({ edit: false })
}
cancelRemove = () => {
this.setState({ remove: false })
}
renderEditItem = () => {
const {
state: {
edit,
remove
},
cancelEdit,
cancelRemove,
handleEdit,
handleRemove
} = this
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
if (remove) {
return (
<div>
<span>Remove view</span>
<br/>
<button onClick={cancelRemove}>Cancel</button>
</div>
)
}
return (
<div>
<button onClick={handleEdit}>Edit</button>
<br/>
<button onClick={handleRemove}>Remove</button>
</div>
)
}
renderUsers = () => {
const {
renderEditItem
} = this
const users = [
{
id: 1,
name: 'User1'
},
{
id: 2,
name: 'User-2'
},
{
id: 3,
name: 'User-3'
}
]
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{renderEditItem()}
</div>
</li>
</ul>
)
})
}
render () {
return (
<div>
{this.renderUsers()}
</div>
)
}
}
React.render(
<App />,
document.getElementById('app')
);
JSfiddle: Here
The issue is how can you see is, when i click on the button to set the state for edit or remove option, this will show the view for all the items,
and should be only the view that is clicked, i know the state change to true and is the same for all the items but i don't know how to set the state only for one entry any idea?
Thank you in advance.
Your problem is that the edit/remove state is singular and for the entire list. Each item in the list receives the same state here:
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
The single edit variable from the state is applied to each list item. If you want to individually set the edit state for each item, it will need to be kept track of with that item.
EX:
const users = [
{
id: 1,
name: 'User1',
edit: true
}]
This way each individual item will be able to tell what state it is in individually. User1 item will have an edit mode that is independent of the other users.
Then you can render something like this:
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{user.edit ? 'EDIT MODE' : 'NOT EDIT MODE'}
</div>
</li>
</ul>
)
})

Categories