I am trying to make an Image slider like e-commerce product in Reactjs.
In javascript it's pretty simple, we only need to change the image source, but how to do it in React? because in React we have to deal with state.
First I mapped out all four images(state) as side images(className ='sideImages') then hardcoded the first image of state as display image. Now I want when I click any of the side images it becomes display image (className='display')
I hope I am clear.
Here is my component
import React, { Component } from 'react';
class App extends Component {
state = {
images: [
{
id: 1,
img: "https://images.pexels.com/photos/1"
},
{
id: 2,
img: "https://images.pexels.com/photos/2"
},
{
id: 3,
img: "https://images.pexels.com/photos/3"
},
{
id: 4,
img: "https://images.pexels.com/photos/4"
}
]
};
onClickHandler = () => {
this.setState({
// this.state.images[0]['img']: 'this.src'
})
}
render() {
const sideImages = this.state.images.map(image => (
<img key={image.id} src={image.img} alt="" />
));
return (
<div className="imageSlider">
<div onClick={this.onClickHandler} className="sideImages">
{sideImages}</div>
<div className='display'>
<img src={this.state.images[0]['img']} alt="" />
</div>
</div>
)
}
}
export default App;
You can take another/better approach of doing this.
Keep 2 variables in your state i.e. image array [don't mutate it] and selectedImageIndex to keep track of currently selected/clicked thubnail.
Whenever you click on any thumbnail image, just change the selectedImageIndex and the target image src can point to <img src={this.state.images[this.state.selectedImageIndex]["img"]} alt="" />
Here is the working example: https://codepen.io/raviroshan/pen/QVraKo
Related
I'd like to add a function to my blogposts where I can click an image and it'll automatically jump to the next image AND text.
I have made a simplistic version without the blog's structure, that works.
Like so: https://i.gyazo.com/81c5baa0348518fd3327c303dac07dfc.mp4
But when I try to modify it to my blog's (Vue) structure, it won't work anymore, what am I doing wrong? I even tried some code I found on stack.
The commented out code is my own original code.
Follow-up question, how would I loop through text? Same way? At first glance I can't think of how.
HTML
<div id="app">
<h1>Test pics</h1>
<div>
<!--<img v-bind:src="images[hovered]" v-on:click="changePicture()" width="100px">-->
<img v-if="image" :key="image" #click="changePicture" class="image" :src="image" alt="image">
</div>
</div>
Vue/Js
new Vue({
el: "#app",
data() {
return {
image:null,
posts: [
{
id:1,
hovered: 0,
images: ['https://via.placeholder.com/150/0000FF/FFFFFF?text=1','https://via.placeholder.com/150/0000FF/FFFFFF?text=2','https://via.placeholder.com/150/0000FF/FFFFFF?text=3'],
text: ['text=1','text=2','text=3']
},
{
id:2,
hovered: 0,
images: ['https://dummyimage.com/600x400/f00/fff','https://dummyimage.com/600x400/00f/fff','https://dummyimage.com/600x400/fbh/fff'],
text: ['dummy=1','dummy=2','dummy=3']
}]
};
},
mounted(){
this.changePicture();
},
methods: {
changePicture() {
this.post.hovered = this.post.images[this.post.hovered];
this.post.hovered = (this.post.hovered + 1) % this.post.images.length;
/*this.post.hovered = (++this.post.hovered)%(this.post.images.length);
console.log(this.post.hovered+1);*/
}
}
})
EDIT:
The code that works:
https://codepen.io/TCGPlaza/collab/QWxJXbp/01272374f7f79eb5efdd4bfcb2bb93f3
I want the working code to basically be replicated for multiple separate posts. Say I make a new post and I add 6 images, then all I have to do is click the image 6 times to make it back to the first image. Other posts might only have 2 images etc...
If I understood you correctly try like following snippet:
First in mounted hook fill images array with objects that contains image, text and index number of image for every element of posts.images array)
In method changePicture pass index, check if nr is lower then images array length, then if it is increment it or if is not start from index 0.
Finally update your images array.
new Vue({
el: "#app",
data() {
return {
images:[],
posts: [{id:1, hovered: 0, images: ['https://via.placeholder.com/150/0000FF/FFFFFF?text=1','https://via.placeholder.com/150/0000FF/FFFFFF?text=2','https://via.placeholder.com/150/0000FF/FFFFFF?text=3','https://via.placeholder.com/150/0000FF/FFFFFF?text=4','https://via.placeholder.com/150/0000FF/FFFFFF?text=5'], text: ['text=1','text=2','text=3','text=4','text=5']}, {id:2, hovered: 0, images: ['https://dummyimage.com/600x400/f00/fff','https://dummyimage.com/600x400/00f/fff','https://dummyimage.com/600x400/fbh/fff'], text: ['dummy=1','dummy=2','dummy=3']}]
};
},
mounted(){
this.posts.forEach(p => this.images.push({img: p.images[0], text: p.text[0], nr: 0}));
},
methods: {
changePicture(idx) {
let nr = this.images[idx].nr
if (nr + 1 < this.posts[idx].images.length) {
nr++
} else {
nr = 0
}
this.images[idx].img = this.posts[idx].images[nr]
this.images[idx].text = this.posts[idx].text[nr]
this.images[idx].nr = nr
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Test pics</h1>
<div v-for="(post, idx) in posts" :key="idx">
<img #click="changePicture(idx)" class="image" :src="images[idx]?.img" alt="image" width="100px">
{{ images[idx]?.text }}
</div>
{{images}}
</div>
I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
I need your help. I try to insert in an array with objects of a photo, to erase and then to deduce. I have no errors in the code, but instead of a photo it shows me a photo icon. All my photos are in a separate folder inside the src folder. Tell me if I'm doing it right or how do I insert a photo into the object and then erase it? Thank you very much
Pizza.js
export let pizza_description = [
{ id: 1,
title: 'title 1',
image: 'bavarska.jpg'},
{ id: 2,
title: 'title: 2',
image: 'salami.jpg'}
]
Pizza_page
import React, {useState} from "react";
import {pizza_description} from "../Food_description/Pizza/Pizza";
export let Pizza_page = () => {
let [pizza, different_pizza] = useState(pizza_description)
return (<div>
{pizza.map(el => <div key={el.id}>
<img key={el.id} src={el.image}/>
<h1>{el.title}</h1>
</div>)}
</div>)
}
You have to import the images or put them in the public directory then reference them like
return (<div>
{pizza.map(el => <div key={el.id}>
// assuming that el.image = image name + extension
<img key={el.id} src={`/pizPhotos/${el.image}`}/>
<h1>{el.title}</h1>
</div>)
}
</div>)
}
I am trying to pass one state value that is imagesArray to another state that is tabData, but it is coming as undefined, PFB the code, please tell what i am doing wrong here?
constructor(props) {
super(props);
this.state = {
imagesArray: [
{
default: '/images/volImage1.png',
active: 'images/volImage1.png'
},
{
default: '/images/volImage2.png',
active: 'images/volImage2-Active.png'
},
{
default: '/images/volImage3.png',
active: 'images/volImage3.png'
},
{
default: '/images/volImage4.png',
active: 'images/volImage4.png'
},
{
default: '/images/volImage5678.png',
active: 'images/volImage5678.png'
},
],
tabData: [
{
title: 'Knowledge and experience',
content: <VolunteerTabContent1 imagesArray={this.state.imagesArray} />
//Here I am passing above imagesArray state, and this is coming as undefined and throwing error
},
{
title: 'Practical and hands on',
content: 'Tab 2 Content'
},
{
title: 'Management and leadership',
content: 'Tab 3 Content'
},
]
}
}
You cannot use this.state when setting the state itself. This won't work at all.
In your case, if imagesArray is not going to be changed during the execution and it's only some data you need, maybe you don't need to set it as part of the component's state.
You could define imagesArray as a constant outside the class or something similar, and just reference it when setting the tabData.
EDIT:
Even more. If tabData is just data you will need afterwards but it's not going to change, you don't need to set that as state either.
EDIT 2:
If this two arrays really need to be in the state, a way to achieve the desired results would be to define only the component name in the 'content' property of each tabData item, and then use that to instantiate it in the render method:
tabData: [
{
title: 'Knowledge and experience',
content: VolunteerTabContent1
},
...
and then in the render method you can do:
// Let's suppose you want the first one as an example. Do this as you need.
const item = this.state.tabData[0];
render() {
<item.content imagesArray={this.state.imagesArray} />
}
This way you'll correctly get the imagesArray form the state. JSX will get item.content's value (the VolunteerTabContent1 component in this case) and render it.
I will show in functional component it will useful for someone.
import "./styles.css";
import image1 from '../image/man.png';
import image2 from '../image/woman.png';
import React,{useState} from 'react';`enter code here`
import Avatar from '#mui/material/Avatar';
export default function App() {
const [choose,setChoose] =useState("")
const [avatar,setAvatar] = useState(image);
return (
<div className="App">
<Avatar
alt="D S"
src={avatar}
sx={{ width: 44, height: 44 }}
/>
<div>
<Avatar className='Avatars' onClick={()=> setChoose(image1)} alt="Guest"
src={image1} sx={{ width: 30, height: 30 }}/>
<label>Guest</label>
</div>
<div>
<Avatar className='Avatars' onClick={()=> setChoose(image2)} alt="Other"
src={image2} sx={{ width: 30, height: 30 }}/>
<label>Other</label>
</div>
<div>
<button className="avatar_btn1" onClick={()=>setAvatar(choose)} >OK</button>
</div>
</div>
);
}
from my code first you can choose a avatar it will not change in frontend when you click ok button it will show avatar changed one.
I'm currently creating a custom React component in Meteor for adding images to a list (and later uploading them). However when I try to delete images from the list, always the last element is removed from the GUI. Initially I thought this was just a simple case of using the wrong index for deletion, but it turned out to be more than that.
This is what my ImageList component currently looks like:
import React from 'react';
import Dropzone from 'react-dropzone';
import cloneDeep from 'lodash.clonedeep';
import { ImageItem } from './image-item.js';
export class ImagesList extends React.Component {
constructor(props) {
super(props);
this.values = this.props.images || [];
this.onDrop = this.onDrop.bind(this);
this.addImages = this.addImages.bind(this);
this.deleteImage = this.deleteImage.bind(this);
this.imageChanged = this.imageChanged.bind(this);
}
onDrop(files) {
this.addImages(files);
}
onDropRejected() {
alert('Invalid file type');
}
addImages(files) {
files.forEach(file => {
this.values.push({
title: '',
description: '',
url: file.preview,
file,
});
});
this.forceUpdate();
}
deleteImage(index) {
console.log('index to delete', index);
console.log('images pre-delete', cloneDeep(this.values)); // deep-copy because logging is async
this.values.splice(index, 1);
console.log('images post-delete', cloneDeep(this.values)); // deep-copy because logging is async
this.forceUpdate();
}
imageChanged(index, image) {
this.values[index] = image;
this.forceUpdate();
}
render() {
console.log('--------RENDER--------');
return (
<div className="image-list">
<div className="list-group">
{this.values.length === 0 ?
<div className="list-group-item">
No images
</div>
:
this.values.map((image, index) => {
console.log('rendering image', image);
return (
<ImageItem
key={index}
image={image}
onDelete={() => { this.deleteImage(index); }}
onChange={(item) => { this.imageChanged(index, item); }}
deletable={true}
/>
);
})
}
</div>
<Dropzone
multiple={true}
onDrop={this.onDrop}
onDropRejected={this.onDropRejected}
className="dropzone"
activeClassName="dropzone-accept"
rejectStyle={this.rejectStyle}
accept={'image/*'}
>
<span>Drop files here</span>
</Dropzone>
</div>
);
}
}
The ImagesList component can be initialized with some values (for the sake of debugging), which it uses during rendering. For example:
<ImagesList images={[
{ title: 'Image 1', description: 'Image 1 description', url: 'http://cssdeck.com/uploads/media/items/3/3yiC6Yq.jpg' },
{ title: 'Image 2', description: 'Image 2 description', url: 'http://cssdeck.com/uploads/media/items/4/40Ly3VB.jpg' },
{ title: 'Image 3', description: 'Image 3 description', url: 'http://cssdeck.com/uploads/media/items/0/00kih8g.jpg' },
]}/>
ImagesList renders an ImageItem component for each image. This is what this component looks like:
import React from 'react';
import { RIEInput, RIETextArea } from 'riek';
export class ImageItem extends React.Component {
constructor(props) {
super(props);
this.placeholder = {
title: 'Title',
description: 'Description',
};
this.value = this.props.image;
}
render() {
return (
<div className="list-group-item">
<div className="text-content">
<h4>
<RIEInput
className="description"
value={this.value.title.length <= 0 ?
this.placeholder.title : this.value.title}
change={(item) => {
this.value.title = item.value;
this.props.onChange(this.value);
}}
validate={(value) => value.length >= 1}
classEditing="form-control"
propName="value"
/>
</h4>
<span>
<RIETextArea
className="description"
value={this.value.description.length <= 0 ?
this.placeholder.description : this.value.description}
change={(item) => {
this.value.description = item.value;
this.props.onChange(this.value);
}}
validate={(value) => value.length >= 1}
classEditing="form-control"
propName="value"
rows="2"
/>
</span>
</div>
<img className="thumb img-responsive"
style={{width: '20%' }}
src={this.value.url}
alt="Image"
data-action="zoom"
/>
{this.props.deletable ?
<div className="delete-btn">
<span onClick={this.props.onDelete}>
×
</span>
</div>
:
undefined }
</div>
);
}
}
Let's say I have three images, image A, B and C, and I want to delete image B. After pressing the delete button, image C will disappear from the GUI instead.
Inside the deleteImage() function of ImagesList, I am logging the index that is to be deleted and also log the values before and after the deletion. The index that is logged is correct, in this case that is index 1. Before the deletion the values are images A, B and C. After deletion the values are images A and C, as they should be.
I decided to do some logging inside the render() function of ImagesList as well. Unfortunately this also logs the correct values A and C, but A and B are actually rendered.
I have also tried to use the React state for this component instead of storing it in a local variable in conjunction with forceUpdate().
Another thing I have tried is to use the React Developer Tools plugin for Chrome. The Devtools also show the correct values, but the GUI still does not, as shown in this screenshot.
I'm currently out of ideas on what to try, any help would be appreciated!
Using the snippets I provided, you should be able to create a Meteor project and reproduce this bug.
With MasterAM's suggestion I managed to find two different solutions.
A.) Using componentWillUpdate()
The this.value variable is set only once namely in the constructor of the ImageItem component. To ensure that changes are properly delegated, you have to update this.value inside the componentWillUpdate() function. Something like:
componentWillUpdate(nextProps, nextState) {
this.value = nextProps.image;
}
B.) Using the property directly
This is definitely the more proper solution. Here we get rid of the local variable this.value inside the constructor of the ImageItem component.
Inside the render() function you replace this.value with this.props.image. Now without having to use the componentWillUpdate() function, everything works as expected.