How to map over array of ImageURLs and display each? - javascript

I am implementing a multiple image upload. The user should have the possibility to select the images and see a small preview of the selected images. I store the image URL in an array and iterate the url to display the images. The problem is that the image is not updated in the preview. I.e. the first, second, etc. Image is the same (see attached picture). When I look at the image URL as text, it becomes clear that the links are different and lead to different images (see picture). So I don't understand why the same image is displayed overall iterations although the url is different.
export class ImageUploadView extends Component {
constructor(props) {
super(props);
this.onChangeImage = this.onChangeImage.bind(this);
this.fileObj = [];
this.fileArray = [];
}
onChangeImage (e) {
this.fileObj.push(e.target.files)
for (let i = 0; i < this.fileObj[0].length; i++) {
this.fileArray.push(URL.createObjectURL(this.fileObj[0][i]))
}
}
render() {
return (
<React.Fragment>
<div className="card card-body mt-4 mb-4">
<h1>Add Image</h1>
<div>
<input type="file" onChange={this.onChangeImage} multiple ref={this.inputRef} />
{(this.fileArray || []).map((url) => (
<MDBRow key={url} style={{ margin: '10px', display: 'inline-block' }}>
<MDBCol>
<MDBCard style={{ width: '5rem' }}>
<MDBCardImage className="img-fluid" src={url} waves />
<MDBCardBody>
<p> {url} </p>
</MDBCardBody>
</MDBCard>
</MDBCol>
</MDBRow>
))}
</div>
</div>
</React.Fragment>
);
}
}

Related

How to add space between item row?

I want to have this design
This is what I have now
export default function Minter({
...
}) {
const uploadButton = (
<div>
Choose file
</div>
);
const uploadView = (
<div style={{ padding: 50 }}>
<p className="uppercase text-gray-400 mb-6 text-center">
PNG, GIF, WEBP, MP4 or MP3. Max 100mb.
</p>
<Upload
name="avatar"
accept=".jpeg,.jpg,.png,.gif"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
beforeUpload={beforeUpload}
>
{uploadButton}
</Upload>
</div>
);
const preview = previewURL ? <img src={previewURL} style={{ maxWidth: "800px" }} /> : <div />
const nameField = (
<Input
style={{
border: 0,
}}
placeholder="Enter a name" onChange={e => {
setName(e.target.value);
}} />
);
const descriptionField = (
<Input.TextArea
placeholder="Enter a description" onChange={e => {
setDescription(e.target.value);
}} />
);
const priceField = (
<Input.TextArea placeholder="Enter a price" onChange={e => {
setPrice(e.target.value);
}} />
);
const mintButton = (
<Button type="primary" disabled={!mintEnabled} onClick={startMinting}>
{minting ? <LoadingOutlined /> : "Mint!"}
</Button>
)
const minterForm = (
<body>
<div style={{
alignItems: 'center',
display: "flex", flexDirection: "column",
}}>
<div style={{justifyContent:'space-between',display: "flex", flexDirection: "row" }}>
<div style={{
borderStyle: 'dotted',
borderRadius: 1,
}}>
{file == null && uploadView}
{preview}
</div>
<div>
<div> Name</div>
{nameField}
<p>Description</p>
{descriptionField}
<p>Price</p>
{priceField}
{mintButton}
{status}
</div>
</div>
</div>
</body>
);
return minterForm;
}
How to add space in middle?
How to align name,description,price to left?
I would like to shorten the height of the "Choose file", make it look exactly like the above image.
Any suggestion would be much appreciated.
It's difficult to answer some of these questions as there's no reproducible code link (CodeSandbox, etc).
The outer flexbox is likely causing some issues as it's probably not letting the children breathe - for these two components you should only need one parent flex with both justify-content: center and align-items: center
gap css property for flex/grid should be your friend here. If adding a gap on the second flex does nothing, see above or add a minWidth.
This is likely due to other styles interfering. Either add an explicit text-align: left, or use the Inspect feature of your browser to see what's happening.
Add a maxHeight to the div, or look into what styles are being applied by the avatar-uploader class.
I'd look into the Material UI library, as the TextField component has similar styling to the reference image.

React: How to display a modal without encountering error message (details provided)

Intro
When my application starts, a list of images appear. If additional details about an image is required, a user can click on an image that has previously loaded, and a modal would appear with it's relevant details.
Even though everything works as it should, I'm encountering an error message that can be found in Chrome browser's console.
Error
Warning: Each child in a list should have a unique "key" prop.
Attempted
I've tried placing key={Math.floor(Date.now() * 20)} within the Image found in the modal, but that doesn't work.
Where am I going wrong? How can I fix this issue?
function getHomePage() {
const [token, setToken] = useState("");
const [visibility, setVisibility] = useState(false);
const [NFTBalances, setNFTBalances] = useState();
const [collection, setCollection] = useState();
const [nft, setNft] = useState();
const { Moralis } = useMoralis();
//load my images when application starts
useEffect(() => {
collectionChanged('myCollection');
}, []);
const handleSelectToken = async (num, col) => {
if (num && col) {
const dbNFTs = Moralis.Object.extend(col);
const query = new Moralis.Query(dbNFTs);
console.log(num);
query.equalTo("tokenId", num);
let selectedNFT = await query.first();
selectedNFT = selectedNFT.attributes;
console.log(selectedNFT);
setNft(selectedNFT);
setVisibility(true);
//open modal
$("#openModal").modal("show");
}
};
const addToNFTs = async (col) => {
const dbNFTs = Moralis.Object.extend(col);
const query = new Moralis.Query(dbNFTs);
query.ascending("rank");
query.limit(4);
const topNFTs = query.skip(NFTBalances.length);
const results = await topNFTs.find();
setNFTBalances(NFTBalances.concat(results));
}
return (
<>
//modal box
<div className="modal fade" id="openModal" tabIndex="-1" role="dialog" aria-hidden="true">
<div className="modal-dialog modal-lg" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<div>
<Image key={Math.floor(Date.now() * 20)}
preview={false} src={nft.image} loading="lazy"
style={{ display: "block", width: "50%"}}/>
<p>{nft.details}</p>
</div>
</div>
</div>
</div>
</div>
<div className="col-xxl-3 col-xl-3 col-lg-3 col-md-3">
<div className="filter-sidebar">
<div className="filter-sidebar-content">
<div className="form-group">
//SEARCH BOX
<Search placeholder="Please search here"
onChange={(e) => setToken(e.target.value) }
onSearch={() => handleSelectToken(token, collection)} />
</div>
</div>
</div>
</div>
//Images populate when application loads
//Clicking an image will populate image details in a modal
<div className="row">
{NFTBalances && NFTBalances.map((nft, index) => {
return (
<div className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
<div className="card items">
<Card key={index} onClick={() =>
handleSelectToken(nft.attributes.tokenId,collection)}
cover={ <Image src={nft.attributes.image} /> }>
</Card>
</div>
</div>
);})}
</div>
</>
);}
export default getHomePage;
You're placing the key at the wrong place. You're not rendering a list of images, but rather a list of cards containing images, here is where you need to add the key prop. You've already added a key to the card itself, butit needs to be present on the parent element. You can use index only if you do not expect the order of these to change during render. Alternatively use your key={Math.floor(Date.now() * 20)}.
{NFTBalances && NFTBalances.map((nft, index) => {
return (
<div key={index} className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
<div className="card items">
<Card onClick={() =>
handleSelectToken(nft.attributes.tokenId,collection)}
cover={ <Image src={nft.attributes.image} /> }>
</Card>
</div>
</div>
);})}
</div>
</>
);}
In modal box the key for the <Image> is causing an issue since the key values need to be unique.
By setting key={Math.floor(Date.now() * 20)}, every <Image> will end up with the same key since they are rendered at the same time.
You should be able to just remove key from that line:
<Image preview={false} src={nft.image} loading="lazy" style={{ display: "block", width: "50%"}}/>
But if that ends up giving you issues, you could also generate a key randomly:
<Image key={Math.random().toString()} preview={false} src={nft.image} loading="lazy" style={{ display: "block", width: "50%"}}/>
I don't know what your nft object looks like, but if each one has its own unique id, that id would be ideal to use for key. This answer explains why that is.
<Image key={nft.id} preview={false} src={nft.image} loading="lazy" style={{ display: "block", width: "50%"}}/>
Another issue is that there is no key being set on the immediate child inside of .map. You should be setting the key on the div that comes right after return:
{NFTBalances && NFTBalances.map((nft, index) => {
return (
<div key={index} className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
<div className="card items">
<Card onClick={() =>
handleSelectToken(nft.attributes.tokenId,collection)}
cover={ <Image src={nft.attributes.image} /> }>
</Card>
</div>
</div>
Good luck! They key prop is pretty important in React. Here's a bit more information from a great answer about key if you want to learn more.
{NFTBalances && NFTBalances.map((nft, index) => {
return (
<div className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
Keys are needed when you make an array of elements, and so this <div> is where you need to put the key. Ideally, it will be some unique identifier found in the nft object. Since i don't know the structure of your data i can't be sure the best value to use, but assuming nft.attributes.tokenId is unique and always present:
<div key={nft.attributes.tokenId} className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
If there is no unique identifier on nft, then as a last resort you could use the index, but this will only work correctly if you are not rearranging this list.
<div key={index} className="col-xxl-3 col-xl-3 col-lg-6 col-md-6">
key={Math.floor(Date.now() * 20)}
This is a bad key. First, it's not going to be unique: all these <div>s are being created at basically the same time, so in all likelyhood they'll all get the same value for Date.now(). Second, they are going to change from one render to the next.
The point of keys in react is to tell react which element in the first render corresponds to which element in the second render. If they're all changing, then react thinks it needs to throw out everything and start again (ie, it must unmount and remount all the components)

How can I add Vanilla JS to my react app to create Image Modal pop-ups?

I am creating an image grid for a website Im making using React and I wanted to add a modal functionality to view each image as a modal pop-up. For this I need to use some Vanilla JS in combination with CSS and HTML. This is the js I want to use:
// Select the modal
var modal = document.getElementById('myModal');
// Get the image inside the modal
var img = document.getElementsByClassName('image');
var modalImg = document.getElementById("img01");
var captionText = document.getElementById("caption");
img.onclick = function(){
modal.style.display = "block";
modalImg.src = this.src;
captionText.innerHTML = this.alt;
}
// Select the close button
var span = document.getElementsByClassName("close");
// Close Button
span.onclick = function() {
modal.style.display = "none";
}
How can I integrate it with the rest of the app?
EDIT:
This is the component I am working on:
import React from "react";
import "../stylesheets/Photos.scss";
const Photos = () => {
return (
<div className="photos_container">
<h1 className="photos_title">PHOTO GALLERY</h1>
<div className="row">
<div className="column">
<img className='image'src="../assets/photos_page/photo2.jpg"/>
<img className='image' src="../assets/photos_page/photo3.jpg"/>
</div>
<div className="column">
<img className='image' src="../assets/photos_page/photo4.jpg"/>
<img className='image' src="../assets/photos_page/photo5.jpg"/>
<img className='image' src="../assets/photos_page/photo6.jpg"/>
</div>
<div className="column">
<img className='image' src="../assets/photos_page/photo7.jpg"/>
<img className='image' src="../assets/photos_page/photo8.png"/>
<img className='image' src="../assets/photos_page/photo9.jpg"/>
</div>
<div className="column">
<img className='image' src="../assets/photos_page/photo10.png"/>
<img className='image' src="../assets/photos_page/photo11.jpg"/>
</div>
</div>
{/* modal */}
<div id='mymodal' className='modal'>
<span className='close'>×</span>
<img className='modal-content' id="img01"/>
<div id='caption'></div>
</div>
</div>
);
};
export default Photos;
Someone beat me to it, but I will still leave my version of the answer here in case you find it helpful. I would not select document elements in the mentioned way, because there is simpler and more declarative way. What you want to do is change this component into a stateful one, which contains a state property like "showmodal". When it is true, the modal should be shown. Then you can toggle the state and set the image which was clicked on by using this.setState({ showmodal: true, slectedImage: yourImage }) in your onClick function. A simple absolutely positioned container should do the trick for the modal container.
Here is an example of what such a component would look like.
const images = [
{
uri:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTm3CuNzOrmWhos3MXI0v_KkyO_xrZ5Y8hFxSYGj_6OtUygZUUX"
},
{
uri:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQR_Fg_lMrPjMa1duHXnvFnZOtsn883LGLD7c2-CwKdfFXAoveQnQ"
}
];
class App extends React.Component {
state = {
showmodal: false,
selectedImage: undefined
};
render() {
//
return (
<div style={{ height: "100vh"}}>
{images.map(i => (
<img
key={i.uri}
src={i.uri}
style={{ margin: 5 }}
onClick={() => this.setState({ showmodal: true, selectedImage: i })}
/>
))}
{this.state.showmodal &&
<div
onClick={() => this.setState({ showmodal: false })}
style={{
display: 'flex',
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: "rgba(0,0,0,0.5)",
justifyContent: 'center', alignItems: 'center'
}}
>
<div style={{ height: 300, width: 500, backgroundColor: '#fff' }}>
<img src={this.state.selectedImage.uri} style={{ flex: 1}} />
<h3>Place any content here</h3>
</div>
</div>}
</div>
);
}
}
React.render(<App />, document.getElementById("app"));
I did not refactor out the styles so you can see what each div is doing as a super basic example which you can check out in codepen. Also, I agree with viz above in trying to refactor you columns so that you can render them the exact same way, and reduce duplication.
Hope it helps!
Everything in react is javascript, so
<div style={{display: this.state.showModal ? 'block' : 'none' }}>
<span onClick={() => {this.state.showModal = false;}}>×</span>
<img src={this.state.currentImage}/>
<div>{this.state.currentCaption}</div>
</div>
and then set respective currentImage and currentCaption with setState upon the image click.
You should also consider programmatically composing the column & images' jsx so that it can be concise.

Image onError Handling in React

I am trying to grab the maxresdefault of multiple YouTube thumbnails. Some YouTube videos simply don't have a high res thumbnail photo so I want to catch that with an onError prop on an img element. For some reason my function is not triggering when I get the 404 img error. Any ideas? Thanks in advance.
class FeaturedVideo extends Component<Props> {
addDefaultSrc = (e) => {
e.target.src = this.props.background.replace("maxresdefault", "hqdefault")
}
renderVideo = (props) => (
<div
style={{
width: "100%",
height: "100%",
backgroundSize: "contain",
}}
className="featured-community-video"
>
<img onError={this.addDefaultSrc} src={props.background} alt="" />
<div className="featured-community-video-title">
<h2 style={{ fontSize: "0.8em" }}>WATCH</h2>
<h1 style={{ fontSize: props.titleFontSize }}>{props.title}</h1>
</div>
</div>
)
render() {
return (
<div
key={this.props.postId}
style={{
width: this.props.width,
height: "50%",
}}
className="featured-community-video-container"
>
<Link to={routeCodes.POST_DETAILS(this.props.postId)}>
{this.renderVideo(this.props)}
</Link>
</div>
)
}
}
To achieve this, I would suggest rendering the <img /> based on the state of your <FeatureVideo/> component.
You could for instance, create an Image object, attempt to load the background image and by this, reliably determine if the image fails to load. On the images success or failure to load, you would then setState() on your <FeaturedVideo/> component with the appropriate background value which would instead be used to rendering you actual <img/> element:
class FeaturedVideo extends Component<Props> {
componentDidMount() {
if(this.props.background) {
// When component mounts, create an image object and attempt to load background
fetch(this.props.background).then(response => {
if(response.ok) {
// On success, set the background state from background
// prop
this.setState({ background : this.props.background })
} else {
// On failure to load, set the "default" background state
this.setState({ background : this.props.background.replace("maxresdefault", "hqdefault") })
}
});
}
}
// Update the function signature to be class method to make state access eaiser
renderVideo(props) {
return <div
style={{
width: "100%",
height: "100%",
backgroundSize: "contain",
}}
className="featured-community-video">
{/* Update image src to come from state */}
<img src={this.state.background} alt="" />
<div className="featured-community-video-title">
<h2 style={{ fontSize: "0.8em" }}>WATCH</h2>
<h1 style={{ fontSize: props.titleFontSize }}>{props.title}</h1>
</div>
</div>
}
render() {
return (
<div
key={this.props.postId}
style={{
width: this.props.width,
height: "50%",
}}
className="featured-community-video-container"
>
<Link to={routeCodes.POST_DETAILS(this.props.postId)}>
{this.renderVideo(this.props)}
</Link>
</div>
)
}
}
Hope that helps!

change setState from user Input

I'm fairly new to React and experimenting with React. I am making a set of cubes that when a user clicks, alternate between colors.
To learn more about setState, I added an input so that when the user enters a color, the color of the cubes changes. The problem is the color of the cube changes, but when I click on the cube it goes back to the default one. I want the cubes to change the colors that are inputted by the user.
What have I tried?
I tried changing pink in this.setState({color: 'pink'}) to setColor(), but it doesn't work. I looked around here, but couldn't find anything that would answer my question.
I've created the issue here.
class TicTacToe extends Component {
state = {
color: 'black',
colors: 'white',
count: 0
}
changeColor(c){
this.setState({ count: this.state.count + 1 })
if(this.state.count % 2 === 0){
this.setState({color: 'pink'})
this.setState({colors: 'grey'})
} else if (this.state.count % 2 !== 0){
this.setState({color: 'grey'})
this.setState({colors: 'pink'})
}
}
setColor(color){
return this.setState({color: color})
}
setColors(color){
this.setState({colors: color})
}
render(){
return (
<div className="main">
<div className="inputFields">
<span> Color One:
<input value={this.state.color}
onChange={(e)=> this.setColor(e.target.value)} /> </span>
<br /><br />
<span> Color Two:
<input value={this.state.colors}
onChange={(e)=> this.setColors(e.target.value)} /> </span>
</div>
<div onClick= {(c)=> this.changeColor()}>
<div className='square' style={{backgroundColor: this.state.color}}></div>
<div className='square' style={{backgroundColor: this.state.colors}}></div>
<div className='square' style={{backgroundColor: this.state.color}}></div>
<br />
<div className='square' style={{backgroundColor: this.state.colors}}></div>
<div className='square' style={{backgroundColor: this.state.color}}></div>
<div className='square' style={{backgroundColor: this.state.colors}}></div>
<br />
<div className='square' style={{backgroundColor: this.state.color}}></div>
<div className='square' style={{backgroundColor: this.state.colors}}></div>
<div className='square' style={{backgroundColor: this.state.color}}></div>
</div>
<span> This is the count: {this.state.count} </span>
</div>
)
}
}
export default TicTacToe;
It goes back to the default onClick, because onClick you are manually setting the colors, Also you don't need a count variable to alternate between colors. The count state is also not used properly. All you need is to use functional setState and implement changeColor like
changeColor(c){
this.setState(prevState => ({
color: prevState.colors,
colors: prevState.color
}))
}
Working Demo
Check When to use functional setState
Check setState doesn't update the state immediately

Categories