How do I use React Modal with a map function? - reactjs

I'm mapping through data, I want to click on an image and have a modal popup with the data.title and data.info. Each modal is only showing the last data from my array. I've read that all the modals are popping up together and I'm only seeing the last one but I don't quite understand how to solve the problem, particularly with function components in React. TIA
export default function SomeComponent
const [modalIsOpen, setModalIsOpen] =
useState(false);
const customStyles = {
content: {
top: '35%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
width: '60%',
transform: 'translate(-40%, -10%)',
},
}
return (
<div className="container">
{ somedata.map((data) =>
(<div className='item' key={data.id} >
<img src={data.img} alt='' onClick={()=> setModalIsOpen(true)} />
<Modal isOpen={modalIsOpen} onRequestClose={() => setModalIsOpen(false)} style={customStyles}>
<h1>{data.title</h1>
<p>{data.content</p>
<div>
<button onClick={() => setModalIsOpen(false)}>X</button>
</div>
</Modal>
</div>))}
</div>
</div>
);}

You should use only one instance of Modal component and pass on the data of the clicked image to it using a state. Each time you click on an image, the data for modal should be updated with the data of the clicked image. See the example below. I've modified your code to make it work.
export default function SomeComponent() {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [modalData, setModalData] = useState(null);
const customStyles = {
content: {
top: '35%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
width: '60%',
transform: 'translate(-40%, -10%)',
},
}
return (
<>
<div className="container">
{somedata.map(data => (
<div className='item' key={data.id} >
<img
src={data.img}
alt=''
onClick={()=> {
setModalData(data);
setModalIsOpen(true);
}
/>
</div>
))}
</div>
<Modal isOpen={modalIsOpen} onRequestClose={() => setModalIsOpen(false)} style={customStyles}>
<h1>{modalData.title}</h1>
<p>{modalData.content}</p>
<div>
<button onClick={() => setModalIsOpen(false)}>X</button>
</div>
</Modal>
</>
)
}

Related

How can I show the div when its display none?

when i click the Cross (X) btn in div its should we close and when I click the Rundown List its show me div example:-
when I click the Business news Cross btn its close the div but when i click the rundown List business news its show me business new
i have try but i when i click the cross btn is again not open the div
Code:-.
Parent component
const LeftNav = () => {
return (
<div className="allDivs">
{item.map((items, index) => {
// console.log(item)
return (
<div key={index} >
<TabHeader item={items} index={index}/>
</div>
)
})}
</div>
</div >
)
}
export default LeftNav;
Child componentL;-
export default function TabHeader({ item, index }) {
const [Close, setClose] = useState(false);
useEffect(() => {
console.log("activeFOCUS", activeFOCUS);
setShow(ontoggle);
}, [])
return (
<Fragment>
<div id="CLOSEDIV" style={Close === true ? { display: "none" } : { display: "block" }}>
<div className="TableText" onClick={(e) => { handleOnClick(e, Delete.val) }}>
<div id="SHOW">{Delete.val}</div>
</div>
//cross btn
<div className="CloseIcon" id="CloseBtn"><FaRegTimesCircle
style={{ color: "#FC0000", width: "20px", height: "20px", alignItems: "right" }}
onClick={(e) => { handleToggle(e, index, Delete.val) }} /></div>
</div>
</div>
</Fragment >
)
}
please help....
Try using visibility: hidden and visibility: visible instead of display: none

How to close only one Div at a time in React?

Code:-
const [Close, setClose] = useState(true)
<div className="allDivs">
{item.map((item, index) => {
// console.log("myDivs", myDivs);
return (
<Fragment key={index} >
<div className="tableHeaderBody" id="CLOSEDIV" style={{display: Close ? 'initial' : 'none'}}>
<div className="TableText">
<div className="TableTextHide"></div> <div style={{ color: "white" }} id="SHOW">{item.val}</div></div>
<div className="CloseIcon" id="CloseBtn"><FaCircle style={{ color: "#FC0000", width: "10px", height: "10px", alignItems: "right" }} onClick={() => setClose(false)} /></div>
</div>
</Fragment>
)
})
}
</div>
I want that when i click the Red circle at any div (show in image) it close the div, but right now when i click the One div red circle its closes all the div
please help.
Try this:
Create a new component ChildComponent:
export default function ChildComponent({item}) {
const [Close, setClose] = useState(true) // Every Child now has it's own setClose controll
return (
<Fragment>
<div className="tableHeaderBody" id="CLOSEDIV" style={{display: Close ? 'initial' : 'none'}}>
<div className="TableText">
<div className="TableTextHide"></div>
<div style={{ color: "white" }} id="SHOW">{item.val}</div>
</div>
<div className="CloseIcon" id="CloseBtn">
<FaCircle style={{ color: "#FC0000", width: "10px", height: "10px", alignItems: "right" }} onClick={() => setClose(false)} />
</div>
</div>
</Fragment>
)
}
Pass the ChildComponent to your component shown above:
<div className="allDivs">
{item.map((item, index) => (
<div key={index}>
<ChildComponent item={item} />
</div>
))}
</div>

How to update height prop with useRef in React?

I need to dynamically define the size of HTML element and set its height to component. I tried to do this with useRef but it doesn't work as expected because of state which contains the previous value (not the current one). Could someone help me with this?
And here's the link: CodeSandBox https://codesandbox.io/s/happy-water-fzqk8?file=/src/App.js
The below code works fine but there's hardcored variable HEIGHT which defines the height of a tab. My task is to make the height dynamic
import { useState } from 'react';
const HEIGHT = {
0: 200,
1: 400,
2: 800,
}
function App() {
const [tab, setTab] = useState(0);
const switchTab = (id) => {
setTab(id);
};
return (
<div
style={{
margin: '100px auto',
backgroundColor: 'pink',
width: '400px',
overflow: 'hidden',
height: HEIGHT[tab], // need this to be dynamic not hardcored
}}
>
<div>
{tab === 0 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 1</h2>
<input />
<button onClick={() => switchTab(1)}>Go to tab 2</button>
<p>Some text here</p>
</div>
)}
{tab === 1 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 2</h2>
<input />
<button onClick={() => switchTab(0)}>Go to tab 1</button>
<button onClick={() => switchTab(2)}>Go to tab 3</button>
<p>
Some more text here. Some more text here. Some more text here. Some more text here.
Some more text here. Some more text here. Some more text here
</p>
</div>
)}
{tab === 2 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 3</h2>
<input />
<button onClick={() => switchTab(0)}>Go to tab 1</button>
<button onClick={() => switchTab(1)}>Go to tab 2</button>
</div>
)}
</div>
</div>
);
}
What I tried:
Added useRef and state which holds the element height
const elRef = useRef(0);
const [height, setHeight] = useState(elRef.current.offsetHeight);
Added function which calculates the size of an element and then sets it to state variable
const resizeHeight = useCallback(() => {
const size = elRef.current.offsetHeight;
setHeight(size)
}, [elRef]);
Added state Height to styles this way
<div
style={{
margin: '100px auto',
backgroundColor: 'pink',
width: '400px',
overflow: 'hidden',
height: height, // it should be the element size
}}
>
It doesn't work((
Here's the link...with the state height - undefined
https://codesandbox.io/s/objective-brown-zq7ih?file=/src/App.js
You can easily update your elRef reference in the switchTab handler without using useEffect and any useCallback hooks:
const elRef = useRef(0);
const SwitchTab = (id) => {
setTab(id);
setHeight(elRef.current.offsetHeight)
};
Now pass the elRef to the ref property of your target div:
return (
<div
style={{
margin: '100px auto',
backgroundColor: 'pink',
width: '400px',
overflow: 'hidden',
height: HEIGHT[tab],
}}
>
<div ref={elRef}> // ------------------------> added here
{tab === 0 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 1</h2>
<input />
<button onClick={() => switchTab(1)}>Go to tab 2</button>
<p>Some text here</p>
</div>
)}
{tab === 1 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 2</h2>
<input />
<button onClick={() => switchTab(0)}>Go to tab 1</button>
<button onClick={() => switchTab(2)}>Go to tab 3</button>
<p>
Some more text here. Some more text here. Some more text here. Some more text here.
Some more text here. Some more text here. Some more text here
</p>
</div>
)}
{tab === 2 && (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<h2>Tab 3</h2>
<input />
<button onClick={() => switchTab(0)}>Go to tab 1</button>
<button onClick={() => switchTab(1)}>Go to tab 2</button>
</div>
)}
</div>
</div>
);

How can I have multiple dropzones on one page that preview multiple images when using react-dropzone?

I am using react-dropzone on my app and would like to have multiple dropzones on one page to preview multiple images. For example, I would like to be able to drop a hero image onto a dropzone that displays the hero image on the top of the page. I would then like to drop a different image onto a different dropzone that displays the image in a thumbnail container.
import React, { useState, useMemo, useEffect } from "react";
import Container from "../components/Container";
import { useDropzone } from "react-dropzone";
const Test = () => {
// Dropzone
const baseStyle = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 2,
borderColor: "#eeeeee",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#bdbdbd",
outline: "none",
transition: "border .24s ease-in-out",
};
const activeStyle = {
borderColor: "#2196f3",
};
const acceptStyle = {
borderColor: "#00e676",
};
const rejectStyle = {
borderColor: "#ff1744",
};
const [files, setFiles] = useState({});
const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject } = useDropzone({
accept: "image/*",
onDrop: (acceptedFiles) => {
console.log(acceptedFiles);
setFiles(
Object.assign(acceptedFiles[0], {
preview: URL.createObjectURL(acceptedFiles[0]),
})
);
},
});
const style = useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {}),
}),
[isDragActive, isDragReject, isDragAccept]
);
useEffect(
() => () => {
// Make sure to revoke the data uris to avoid memory leaks
URL.revokeObjectURL(files.preview);
},
[files]
);
return (
<Container>
{/* This would be the dropzone for the Hero image */}
<div>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<span style={{ fontSize: ".8rem" }}>Drop hero image here, or click to select file</span>
</div>
</div>
{/* This would be the dropzone for the Thumbnail image */}
<div>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<span style={{ fontSize: ".8rem" }}>Drop hero image here, or click to select file</span>
</div>
</div>
{/* This would be where the Hero image is displayed */}
<img
style={{ width: "600px", height: "200px", margin: "0", display: "block" }}
src={files.preview ? files.preview : "https://via.placeholder.com/600x200"}
alt="Hero Image"
/>
{/* This would be where the Thumbnail image is displayed */}
<img
style={{ width: "600px", height: "200px", margin: "0", display: "block" }}
src={files.preview ? files.preview : "https://via.placeholder.com/600x200"}
alt="Thumbnail Image"
/>
</Container>
);
};
export default Test;
I'm guessing I need to modify the onDrop function but I can't figure how to do this. Any help would be greatly appreciated!
Create two separate file variables and handle them separately.
use two separate getRootsProps and getInputProps on both inputs.
const [file, setFile] = useState({});
const [fileGallery, setFileGallery] = useState({});
const { getRootProps:getRootfileProps, getInputProps:getInputfileProps } = useDropzone({
accept: 'image/*',
onDrop: (acceptedFile) => {
setFile(
Object.assign(acceptedFile[0], {
preview: URL.createObjectURL(acceptedFile[0]),
}),
);
},
});
const { getRootProps:getRootGalleryProps, getInputProps:getInputGalleryProps } = useDropzone({
accept: 'image/*',
onDrop: (acceptedFile) => {
setFileGallery(
Object.assign(acceptedFile[0], {
preview: URL.createObjectURL(acceptedFile[0]),
}),
);
},
});
return (
<Container>
{/* This would be the dropzone for the Hero image */}
<div>
<div {...getRootfileProps({ style })}>
<input {...getInputfileProps()} />
<span style={{ fontSize: ".8rem" }}>Drop hero image here, or click to select file</span>
</div>
</div>
{/* This would be the dropzone for the Thumbnail image */}
<div>
<div {...getRootGalleryProps({ style })}>
<input {...getInputGalleryProps()} />
<span style={{ fontSize: ".8rem" }}>Drop hero image here, or click to select file</span>
</div>
</div>
{/* This would be where the Hero image is displayed */}
<img
style={{ width: "600px", height: "200px", margin: "0", display: "block" }}
src={files.preview ? files.preview : "https://via.placeholder.com/600x200"}
alt="Hero Image"
/>
{/* This would be where the Thumbnail image is displayed */}
<img
style={{ width: "600px", height: "200px", margin: "0", display: "block" }}
src={files.preview ? files.preview : "https://via.placeholder.com/600x200"}
alt="Thumbnail Image"
/>
</Container>
);
};
export default Test;
you need to create two separate file variables and handle them separately. also you are using same getRootsProps and getInputProps on both inputs which is not correct.
const [file, setFile] = useState({});
const [fileGallery, setFileGallery] = useState({});
const { getRootProps:getRootfileProps, getInputProps:getInputfileProps } = useDropzone({
accept: 'image/*',
onDrop: (acceptedFile) => {
setFile(
Object.assign(acceptedFile[0], {
preview: URL.createObjectURL(acceptedFile[0]),
}),
);
},
});
const { getRootProps:getRootGalleryProps, getInputProps:getInputGalleryProps } = useDropzone({
accept: 'image/*',
onDrop: (acceptedFile) => {
setFileGallery(
Object.assign(acceptedFile[0], {
preview: URL.createObjectURL(acceptedFile[0]),
}),
);
},
});
You should use two separate filenames on the state one for hero one for thumbnail and manage each of them for each dropzones
something like this:
const [heroFiles, setHeroFiles] = useState({});
const [filesThumb, setFilesThumb] = useState({});
you are using the Dropzone hook and that will make it difficult to achieve what you are intending to, use the Dropzone component instead and declare state for each Dropzone input you intend to use.
import React, { useState } from "react";
import Container from "../components/Container";
import Dropzone from "react-dropzone";
const Test = () => {
const [heroFiles, setHeroFiles] = useState([]);
const [thumbnailFiles, setThumbnailFiles] = useState([]);
return (
<Container>
{/* This would be the dropzone for the Hero image */}
<Dropzone onDrop={(acceptedFiles) => {
setHeroFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}} name="heroImage" multiple={false}>
{({getRootProps, getInputProps}) => (
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<span style={{ fontSize: ".8rem" }}>
Drop hero image here, or click to select file
</span>
</div>
)}
</Dropzone>
{/* This would be the dropzone for the Thumbnail image */}
<Dropzone onDrop={(acceptedFiles) => {
setHeroFiles(acceptedFiles.map(file => Object.assign(file, {
preview: URL.createObjectURL(file)
})));
}} name="heroImage" multiple={false}>
{({getRootProps, getInputProps}) => (
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<span style={{ fontSize: ".8rem" }}>
Drop hero image here, or click to select file
</span>
</div>
)}
</Dropzone>
{/* This would be where the Hero image is displayed */}
<img style={{ width: "600px", height: "200px", margin: "0", display: "block" }} src={heroFiles.length > 0 ? heroFiles[0].preview : "https://via.placeholder.com/600x200"} alt="Hero Image"/>
{/* This would be where the Thumbnail image is displayed */}
<img style={{ width: "600px", height: "200px", margin: "0", display: "block" }} src={thumbnailFiles.length > 0 ? thumbnailFiles[0].preview : "https://via.placeholder.com/600x200"} alt="Thumbnail Image"/>
</Container>
);
}
export default Test;
this is supposed to take care of your issues.

What is best way to do lazy loading in a Next js Application?

I checked the documentation about Lazy Loading components on the official next js docs page (https://nextjs.org/learn/excel/lazy-loading-components).
I tried the steps mentioned and it did not work for me. Below is the piece of code that I want to lazy-load:
<div id="cards" className={index.sectionCards} style={{paddingBottom: '0px'}}>
<div className="title" style={{marginBottom: "0px", paddingBottom: "0px"}}>
Take a Look at Our Exciting Range of Cards
</div>
{this.renderCards()}
<div></div>
</div>
Here the renderCards function makes a call to a backend API and gets images from AWS S3, this whole process takes a lot of time and hence increases the overall page load time, below is the code for the function renderCards:
renderCards() {
const keys = Object.keys(this.state.products);
const valid_keys = ['Specials', 'New Beginnings', 'Expressions', 'Celebrations' ];
if(keys.length == 0) return <div></div>
return (<div className={index.cards}>
{
keys.map((key) => {
if(valid_keys.indexOf(key) > -1) return <div style={{ width: '80%', margin: '0 auto' }}>
<div className={index.category}>{key}</div>
<div style={{ display: 'flex', overflow: 'scroll' }} >
{this.state.products[key].map((c) => {
if(c.status == 'PRODUCT_ACTIVE') {
return <img onClick={() => this.onClickProduct(c)} className={index.cardImage} src={`<backend URL here>`} />
}
})}
</div>
</div>
})
}
</div>)
}
The objective was to lazy load this component to improve the overall page speed.
If anyone knows a way to solve this problem, please share.
Documentation for dynamic/lazy loading with nextjs
const Cards = () => {
const renderCards = () => {
const keys = Object.keys(this.state.products);
const valid_keys = [
"Specials",
"New Beginnings",
"Expressions",
"Celebrations",
];
if (keys.length == 0) return <div></div>;
return (
<div className={index.cards}>
{keys.map((key) => (
<Fragment key={key}>
{valid_keys.indexOf(key) > -1 && (
<div style={{ width: "80%", margin: "0 auto" }}>
<div className={index.category}>{key}</div>
<div style={{ display: "flex", overflow: "scroll" }}>
{this.state.products[key].map((c) => (
<Fragment key={c.id}>
{c.status === "PRODUCT_ACTIVE" && (
<img
onClick={() => this.onClickProduct(c)}
className={index.cardImage}
src={`<backend URL here>`}
/>
)}
</Fragment>
))}
</div>
</div>
)}
</Fragment>
))}
</div>
);
};
return (
<div
id="cards"
className={index.sectionCards}
style={{ paddingBottom: "0px" }}
>
<div
className="title"
style={{
marginBottom: "0px",
paddingBottom: "0px",
}}
>
Take a Look at Our Exciting Range of Cards
<div>{this.renderCards()}</div>
</div>
</div>
);
};
```
above code is not my code but a replication of the code in the question.
```
```
from nextjs
For the best understanding of dynamic/lazy load see link provided.
import dynamic from "next/dynamic";
```
import LoadSpinner from "../loadSpinner";
const Cards = dynamic(() => import("./cards"), {
```
this can be a custom loader or a node_module installed or just <div>Loading...</div> the loading: function will display while waiting for the import fucntion to load.
loading: () => <LoadSpinner />,
```
});
const CardContainer = () => ( <Cards /> );

Resources