I am new to ReactJS and I was wondering what is the correct way to target next element with same class in react?
<div className="portfolioGallery">
<img className="portfolioImg activeImg" src="img/1.png"/>
<img className="portfolioImg" src="img/2.png"/>
<img className="portfolioImg" src="img/2.png"/>
<div className="portfolioNext" onClick={this.nextImg.bind(this)}>
Next image
</div>
</div>
What would be the correct way that when I click the portfolioNext div I would be able to give the img2 class of activeImg and remove it from the previous element and so on in ReactJS?
Thank You!
constructor() {
super();
this.state = {
default: "portfolioImg activeImg"
};
}
nextImg() {
this.setState({
default: "portfolioImg"
});
}
That's the kind of imperative technique that you'd generally find in jQuery code, but it doesn't map very well to React's slightly more declarative nature.
Rather than trying to find the next element with a class, use state to maintain a list of those elements alongside an index cursor.
// constructor
this.state = {
images = ['img/1.png', 'img/2.png', 'img/3.png']
cursor: 0
};
Then use these bits of data to render your view.
// render
const { images, cursor } = this.state;
return (
<div>
{images.map((src, index) => {
const activeClass = (index === cursor) ? 'activeImg' : '';
return <img className={`portfolioImg ${activeClass}`} />;
}}
</div>
);
To change the active image, use setState to change the cursor property.
// nextImg
const { cursor, images } = this.state;
const nextCursor = cursor % images.length;
this.setState({ cursor: nextCursor });
I wouldn't suggest you think of it as siblings finding each other, but instead thing of it as the parent storing an index of the current children, and updating that instead.
this.props (or this.state) would have something like this (pseudocode)
this.props.images => ["img/1.png", "img/2.png", "img/2.png"];
Inside render:
<div className="portfolioGallery">
{this.props.images.map((image, i ) => active === i ? (<img className="portfolioImg activeImg" src="image" key={i}/>) : (<img className="portfolioImg " src="image" key={i}/>))}
<button className="portfolioNext" onClick={(e) => this.setState({active: this.state.active + 1}).bind(this)}>Next image</button>
</div>
Of course, accounting for when active >= images.length, but you get the idea
Related
I am making a game with react, it's a memory game where you match images.so what I want to archive is, when I click on the div it selects the child of the div(the image) and then add class to the parent and then add class to the child itself. Based on the game logic I have to select the the child element first then if pass some conditions I then add a class to it and it's parent element. look at the code I currently have but it's not working, please help me out.
`
let currentElement;
const imageclick=(e)=>{
//select current image
currentElement=e.target.firstElementChild;
// some game logic the add class to child and parent
//add class to parent
currentElement.closest("div").classList.add("show");
//add class to child
currentElement.classList.remove("hidden")
}
const app=()=>{
return(
<div className="imgDiv transition" key={i} onClick={imageclick}>
<img src={img} alt="" className="tileImage hidden" />
</div>
)
}
`
There are multiple approaches.
One way is to store your images in an array of objects. This array is used to render all of your images. You could even shuffle the array to make the order random.
Inside your component you have a state. This state tracks the index of the currently selected image of the array. The initial state can be null to indicate that there is no current selected image.
While looping over your images, check for each image if the selectedImageIndex is equal to the index of the current image. If so, pass some extra classes.
(You don't need to toggle hidden class on the image. Use the show class on the div to either show or hide the child image).
Pass the index of the current image to the imageClick function in the onClick handler of the div. Whenever we click an image, we want to set the index of the image as our selectedImageIndex.
The component will now rerender and add the class to the clicked div.
Edit
I've modified the answer according to your comment. This example allows for 2 indexes to be stored into the state that tracks the selected images.
Whenever you click the same, the image is deselected. Whenever you click another image it will add its index to the state.
In the useEffect hook you can asses if the images corresponding to the indexes have a similar src or other property that matches.
(I would recommend creating a more robust system in which YOU say which images are the same instead of depending on the URL to be the same. E.g. two images can be the same while having different URL's)
const images = [
{
id: 'A',
src: 'your-image.jpg',
alt: 'Something about your image'
},
{
id: 'B'
src: 'your-other-image.jpg',
alt: 'Something about your image'
},
{
id: 'A' // The match to the other A
src: 'the-other-image-that-matches-the-first.jpg',
alt: 'Something about your image'
}
];
const App = () => {
const [selectedImageIndexes, setSelectedImageIndexes] = useState([null, null]);
const imageClick = index => {
setSelectedImageIndex(currentIndexes => {
// If the same index is clicked, deselect all.
if (currentIndexes.includes(index)) {
return [null, null];
}
// If no indexes have been set.
if (currentIndexes.every(index => index === null)) {
return [index, null];
}
// Set the second clicked image.
return [currentIndexes[0], index];
});
};
useEffect(() => {
// If both indexes are set.
if (selectedImageIndexes.every(index => index !== null) {
/**
* With the indexes in the selectedImageIndexes state,
* check if the images corresponding to the indexes are matches.
*/
if (selectedImageIndexes[0].id === selectedImageIndexes[1].id) {
// Match!
}
}
}, [selectedImageIndexes]);
return (
<div className="images">
{images.map((image, index) => {
const className = selectedImageIndex.includes(index) ? 'show' : '';
return (
<div className="imgDiv transition" key={`img-div-${index}`} onClick={() => imageClick(index)}>
<img src={image.src} className="tileImage" alt={image.alt} />
</div>
);
})}
</div>
)
};
i am trying to implement an ui requirement. I want to add a active class name to the children div one at a time. 1st it will add the class in first child, and then the class will be removed and to be added in the 2nd child div. And it will infinitly itereate.
Here is my code in next js
$(".softwares_container").each(function () {
(function ($set) {
setInterval(function () {
var $cur = $set
.find(`.${st.active}`)
.removeClass(`${st.active}`);
//store inner html of current item
var $next = $cur.next().length
? $cur.next()
: $set.children().eq(0);
$next.addClass(`${st.active}`);
//store inner element of next item
//set inner html of current item to inner html of next item
var $next_inner = $next.children().eq(0);
setValue({
name: $next_inner.attr('alt'),
description: $next_inner.attr('data-info')
})
// setImage($next_inner.attr('src'))
}, 1000);
})($(this));
});
<div className={`softwares_container ${st.left_container}`}>
<div className={` ${st.img}`} alt="1">
<img src={ae.src} data-info="this is aftereffects" alt="After effects" />
</div>
<div className={st.img} alt="2">
<img src={pr.src} alt="Adobe Premiere pro" />
</div>
<div className={st.img}>
<img src={ps.src} alt="Adobe Photoshop" />
</div>
<div className={st.img}>
<img src={xd.src} alt="Adobe Xd" />
</div>
</div>
But it is not working.it is showing unexpected behaviour. It works fine in react .
Can anyone please give me an alternative solution or tell me how to fix the issue?
Here's the link where you can see the unexpected behaviour.
https://diptnc.ml/about
You can write an effect that sets the classname for elements in an array in a round-robin manner.
// Keep the interval id around so that
// it can be cleared when unsubscribing the effect.
let activeFxId;
/*
Applies active class to an array of HTMLElement in a round-robin manner.
*/
function activeFx(elements) {
activeFxId = setInterval(() => {
const elementsArr = [...elements];
let idx = elementsArr.findIndex((element) => {
return element.classList.contains('active');
});
if (idx === -1) {
idx = 0;
}
elementsArr[idx].classList.remove('active');
elementsArr[(idx + 1) % elementsArr.length].classList.add('active');
}, 2000);
return () => {
clearInterval(activeFxId);
};
}
How you provide this array of elements is left to you. An approach is to store a ref to the parent element containing them and pass that on to the function.
For example,
/* Example component */
import React, {useEffect, useRef} from 'react';
export default () => {
const ref = useRef();
useEffect(() => {
if (ref.current && ref.current.children) {
return activeFx(ref.current.children);
}
});
return (
<div ref={ref}>
<div>One</div>
<div>Two</div>
<div>Three</div>
</div>
);
};
I have a function which creates 2 divs when changing the number of items correspondingly (say if we choose 5 TVs we will get 5 divs for choosing options). They serve to make a choice - only one of two possible options should be chosen for every TV, so when we click on one of them, it should change its border and background color.
Now I want to create a dynamic stylization for these divs: when we click on them, they should get a new class (tv-option-active) to change their styles.
For that purposes I used 2 arrays (classesLess and classesOver), and every time we click on one of divs we should remove a class if it's already applied to the opposite option and push the class to the target element - thus only one of options will have tv-option-active class.
But when I click on a div I do not get anything - when I open the document in the browser and inspect the elements, the elements do not even receive new class on click - however, when I console log the classes variable that should apply to an element, it is the way it should be - "less tv-option-active" or "over tv-option-active". I applied join method to remove the comma.
I checked the name of imported css file and it is ok so the problem is not there, also I applied some rules just to make sure the problem is not there and it worked when it's not dynamic (I mean no clicks are needed).
So my list of reasons causing that trouble seems to be not working.
I also tried to reorganize the code in order to not call a function in render return - putting mapping directly to render return, but this also didn't work.
Can anyone give me a hint why it is that?
Here is my code.
import React from 'react'
import { NavLink, withRouter } from 'react-router-dom'
import './TVMonting.css'
import PageTitle from '../../PageTitle/PageTitle'
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
},
}
render() {
let classesLess = ['less']
let classesOver = ['over']
const tvHandlers = {
tvs: {
decrease: () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
},
increase: () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
},
},
}
const createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
return divsArray.map((i) => {
return (
<React.Fragment key={i}>
<div
className={classesLess.join(
' '
)}
onClick={() => {
const idx = classesOver.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesLess.splice(
idx,
1
)
}
classesLess.push(
'tv-option-active'
)
}}
>
Under 65
</div>
<div
className={classesOver.join(
' '
)}
onClick={() => {
const idx = classesLess.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesOver.splice(
idx,
1
)
}
classesOver.push(
'tv-option-active'
)
// classesOver.join(' ')
}}
>
Over 65
</div>
</React.Fragment>
)
})
}
return (
<div>
<button onClick={tvHandlers.tvs.decrease}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={tvHandlers.tvs.increase}>
+
</button>
{createDivs()}
</div>
)
}
}
export default withRouter(TVMontingRender)
CSS file is very simple - it just adds a border.
P.S. I know that with this architecture when I click on one of the divs all the divs will get tv-option-active class, but for now I just want to make sure that this architecture works - since I'm relatively new in React 🙂Thanks in advance!
Components won't have their lifecycle triggered if you are mutating a variable. You need a state for that purpose, which stores the handled data.
In your case you need some state to say which div has the active class, under or over. You can also abstract each rendered Tv to another Class component. This way you achieve independent elements that control their own class, rather than changing all others.
For that I created a Tv class, where I simplified some of the logic:
class Tv extends React.Component {
state = {
activeGroup: null
}
// this will update which group is active
changeActiveGroup = (activeGroup) => this.setState({activeGroup})
// activeClass will return 'tv-option-active' if that group is active
activeClass = (group) => (group === this.state.activeGroup ? 'tv-option-active' : '')
render () {
return (
<React.Fragment>
<div
className={`less ${ activeClass('under') }`}
onClick={() => changeActiveGroup('under')}
>
Under 65
</div>
<div
className={`over ${ activeClass('over') }`}
onClick={() => changeActiveGroup('over')}
>
Over 65
</div>
</React.Fragment>
)
}
}
Your TvMontingRender will be cleaner, also it's better to declare your methods at your class body rather than inside of render function:
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
}
}
decreaseTvs = () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
}
increaseTvs = () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
}
createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
// it would be better that key would have an unique generated id (you could use uuid lib for that)
return divsArray.map((i) => <Tv key={i} />)
}
render() {
return (
<div>
<button onClick={this.decreaseTvs}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={this.increaseTvs}>
+
</button>
{this.createDivs()}
</div>
)
}
}
Note: I didn't change the key you are passing to Tv, but when handling an array that you manipulate somehow it's often better to pass an unique id identifier. There are some libs for that like uuid, nanoID.
When handling complex class logic, you may consider using libs like classnames, that would make your life easier.
So i have an array like this const divs = ["Text 1","Text 2","Text 3"].
I create divs (a small menu) from this array in my render function
var createThreeDivs = divs.map((category) => {
return <div key={category} onClick={this.handleClick} className="myDivClass">{category}</div>
});
I want to style one of these divs when i click on them, and the remove the styling on the rest of them. So when i select one of the divs it gets a color and removes the color on the rest of them
In normal javascript with no virtual DOM i could do like this:
handleClick(e) {
//remove styling from others
var allDivs = document.getElementsByClassName("myDivClass");
for(var i = 0; i < allDivslength; i++) {
allDivs[i].classList.remove("myDivClass-styled");
}
//Add styling class to selected,
e.target.classList.add("myDivClass-styled");
}
But this manipulate the DOM directly. How do i do something like this in React?
I have seen examples of how this can be done using state with only one element and by not having an array creating the divs. But i can't come up with a good solution for this scenario. Any suggestions?
Using the component's state you can update the color based on the active div. Update the index of the active div when the user clicks, and when the index equals the div that was clicked on update the color of that div.
See example below.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
active: 0
};
}
render() {
const divs = ["Text 1", "Text 2", "Text 3"];
const updateActiveDiv = (value) => {
this.setState(() => {
//this line will reset the value to
//null if same element is clicked twice
if(value === this.state.active) {
value = null;
};
return {
active: value
}
});
};
let divText = divs.map((div, i) => {
let color = this.state.active === i ? 'red' : 'black';
return <div style={{ color }} onClick={() => updateActiveDiv(i)}>{div}</div>;
});
return (
<div>
{ divText }
</div>
);
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Pass current text from html
handleClick = (text)=>{
this.setState({activeText:text})
}
Inside create div function add class dynamically
Div className = { stat condition ? Class : no class }
How do I get the values of array.map outside of it?
For example:
array.map(index, item) => { return ( <Text>{item.ID}</Text> ) }
Ouside of it I have something like:
<Button onPress={() => this.editItem(CURRENT_ITEM_ID)}></Button>
Where CURRENT_ITEM_ID is the current item on a Card, for example.
Like Tinder I have an array of Cards to Swipe, that I'm mapping, but the Like button is not part of the Card. This way I'm not moving the Like button with the Card, instead it is static, I'm just moving the content of the card. So I need a way to access the current item from outside of the map. The Card has Text and ID. The component below have a Button that I need to click passing the item.ID of the current Card.
Is there a way to set this item to a state?
Thank you.
One solution is to create a state property that holds the id of the card that is showing, and then when the button is clicked, you grab that state and do something with it. Here is an example with onClicks and divs.
const arr = ['card1', 'card2', 'card3', 'card4'];
class App extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleClick(index) {
this.setState({ visibleCard: index });
}
handleButtonClick() {
console.log('this.state.visibleCard', this.state.visibleCard);
console.log('visibleCard', arr[this.state.visibleCard]);
}
render() {
return (
<div>
{arr.map((card, i) => <div onClick={() => this.handleClick(i)}>{card}</div>)}
<button onClick={this.handleButtonClick}>test</button>
</div>
)
}
}
The basic idea is that you tie the index to the card. A handler then sets that state to visible(not sure what this would be in your case since it seems like a mobile app). Then in your button, you use the state to grab the visible card and pass the data to whatever other function you need.
What I normally do is bind the event in the map function.
array.map( (item, key) => {
return (
<div>
<Text>{item.ID}</Text>
<button onClick={this.editItem.bind(this, item.ID)}>Edit</button>
</div>
)
}
Not sure what your HTML looks like, but you need to bind the function params in the map.