React hook's state using event to target a specific div - javascript

I'm new to coding and react and i'm trying to update x4 divs so when the user clicks it will update the content inside. Currently i have the code updating however it updates all 4 div's instead of the specific div which is clicked.
I know this will be controlled by using event however slightly confused what exactly i need to include.
Any help in the correct direction would be greatly appreciated. Current code runs fine but content within all 4 divs update rather than the specific one which is clicked
const [isActive, setIsActive] = useState(false)
const toggleContent = e => {
e.preventDefault()
setIsActive(!isActive)
}
Example div i'm trying to update when user clicks (currently i have x4 others which are the same logic but different HTML content. Currently when any of the 4 buttons are clicked, all x4 divs content change however i only want it to change the specific div which contained the button that was clicked
{!isActive ?
<article>
<h1>BEFORE CLICK TEXT</h1>
<a onClick={toggleClass}>BUTTON</a>
</article>
:
<article>
<p>AFTER CLICK TEXT</p>
<a onClick={toggleClass}>Back</a>
</article>
}

maintain activeIndex instead of just active boolean. In render method, based on activeIndex and index decide the active attributes for div.
Try the snippet.
const content = ['abc', 'def', 'ghi', 'jkl'];
const Component = () => {
const [activeIndex, setActiveIndex] = React.useState(-1);
return (
<div>
{content.map((item, index) =>
<div key={item}
style={{color: activeIndex === index ? "red" : "black" }}
onClick={() => setActiveIndex(index)}> {item}
</div>)}
</div>
)
}
ReactDOM.render(<Component />, document.getElementById('app'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="app"> </div>

Related

How to change/update className of a "nested" grandparent from a button, inside of a child component in react : SOLVED :

As the title says, I have a homepage which uses a map() function to map through an arrayList which holds the information about songs, like title, artist, songURL and img.
When I map() through it I will have many "songs" like you would have with blog posts. many elements which is an article which shares the same classname.
Within this map() function I assign a component to each object mapped which is called , this component renders waveforms etc.. the audiovisualizer component also have a button inside for Play/Pause features. and this is the button I want to interact with the Homepage.
When this button is clicked, I want to change the classname for this specific element.
Through useState and callback functions, I got it almost working, but instead of changing the classname for the specific elements that is related to the button clicked, ALL of the "elements/songs" where changed, and not the one targeted.
I will show you the code down under, so you can see the "Tree" and get an understanding of the code.
My result, would be that the button that is attached to each "song" element, would update that specific parent/grandparent element sit sits inside of. but I can't really seem to wrap my head around the logic.
Im completely new to react, this is my first project ever, so go easy on me.
There is also pictures to see what what the code is doing. My goal is to make the specific play/pause object to have an CSS class that will pull up that song and have a position so you can't scroll while its playing. I want to add an pseudo-element to change background color so you can only see that specific card from the list of cards.. from the picture you can see 2 songs. when I click the play/pause I want that card to get an absolute background that will cover the other song.. Problem is that when I try to onClick={} the button that is nested, I can't reach the event.target... and if I do, all of the are changed, not the specific parent element that holds the button.
EDIT - SOLVED
solved the entire problem by referencing the button using useRef. then I used that element like this let buttonRef = buttonRef.current.closest("my wanted parent by classname").classname, and then I changed that manually.
CODE:
Home.js
** this is where the songs/cards are made swith the map() function // be aware, I removed unrelated code for easier view.. **
const Home = (props) => {
const [songs, setSongs] = useState([]);
return (
<div className='home_wrapper'>
<>
{loading ?
<ClipLoader color="#36d7b7" />
:
<div className='homepage_container'>
<h1 className='homeTitleSongs'>Songs</h1>
<button className='logOutButton' onClick={handleLogout}>Logout</button>
{/* // Map through our song library and display all items on homepage */}
{ songs.map((data) => {
return (
// I WANT TP TARGET THE <Article> element´s classname and edit this when the button from inside <AudioVisualizer> component gets clicked.
// Problem is that this is a map function, so when i did get to change it, it changed ALL of the elements from the map function, not the one specific
// where the button is attached to.
<article key={data.id} className='card'>
<div className='card_content'>
<img className='card_image' src={data.image} alt=""/>
<div className='song_info'>
<h2>{data.title}</h2>
<h4>{data.artist}</h4>
</div>
<div className='audio_wrapper'>
<AudioVisualizer audio={data.audio}/>
</div>
</div>
</article>
)
})}
</div>
}
</>
</div>
)
}
export default Home
audioVisualizer component::
const AudioVisualizer = (props) => {
const [isPlaying, setIsPlaying] = useState(false)
const [volume, setVolume] = useState(0.5);
const playButton = faCirclePlay;
const pauseButton = faCirclePause;
const audioRef = useRef(null);
const audioTrackRef= useRef(undefined);
// Create audio waveform object, and load song from database..
useEffect(()=>{
if (audioRef.current){
audioTrackRef.current = wavesurfer.create({
container: audioRef.current,
progressColor: "#13AEA2",
waveColor: "red",
cursorColor: "OrangeRed",
preload: true,
backend: "WebAudio", // originally = "MediaElement"
barWidth: 2,
barHeight: 1, // the height of the wave
fillParent: true,
hideScrollbar: true,
responsive: true,
});
audioTrackRef.current.load(props.audio);
}
}, [])
// Handle play pause button
const handlePlayPause = (e) => {
// Get a view of what the "click" registers:
// if playing == pause
if ( ! isPlaying ) {
console.log("not playing.. Start playing");
audioTrackRef.current.play()
setIsPlaying(isClicked => true)
return
}
else {
console.log("Is playing.. will pause")
audioTrackRef.current.pause()
setIsPlaying(isClicked => false);
return
}
};
return (
<>
<div className='audio' ref={audioRef}>
</div>
<div className='audioKnobs'>
// This is the button i click for play pause a song inside a specific "song" card. and this card is the one i want to update the classname of..
<button className="playpausewrapper" onClick={handlePlayPause}>
<FontAwesomeIcon className={ isPlaying ? 'playButton activeButton' : 'playButton notActiveButton'} icon={ isPlaying ? pauseButton : playButton} />
</button>
<input type="range" className='VolumeSlider onPhoneRemoveVolumeSlider' id="volume" name="volume" min="0.01" max="1" step=".025" onChange={onVolumeChange} defaultValue={volume}/>
</div>
</>
)
}
export default AudioVisualizer;

How to remove added classes from an element in reactjs

I am making a drag n drop sort of game where you match the logos with their corresponding name.
If user matches the logo with the name correctly than the field which you could drop the logo gets additional classes.
Like this:
if (isCorrectMatching) {
event.target.classList.add("dropped");
draggableElement.classList.add("dragged");
event.target.classList.add("dragged");
event.target.setAttribute("draggable", "false");
draggableElement.setAttribute("draggable", "false");
event.target.innerHTML = `<i class="fab fa-${draggableElementBrand}" style="color: ${draggableElement.style.color};"></i>`;
}
If every match is found user can go to next level , my problem is that these additional classes are staying there , how do I remove them ?
I am mapping them out like this:
<div className="containerItems">
{draggableItems.map((x, i) => {
return (
<div className="draggable-items">
<i
onDragStart={(e) => dragStart(e)}
className={`draggable fab fa-${x}`}
id={x}
draggable="true"
ref={draggableOnes.current[i]}
></i>
</div>
);
})}
</div>;
{
matchingPairs.map((x, i) => {
return (
<section className="matching-pairs">
<div className="matching-pair">
<span className="label">{x}</span>
<span
className="droppable"
// ref={droppableOnes.current[i]}
onDragEnter={(e) => dragEnter(e)}
onDragOver={(e) => dragOver(e)}
onDragLeave={(e) => dragLeave(e)}
onDrop={(e) => drop(e)}
data-brand={x}
></span>
</div>
</section>
);
});
}
I can not seem to solve this one, like how do I remove all the classes that I've added when there was a correct matching.
I would like to remove basically everything that I've added in my if (isCorrectMatching) .
I've tried to use refs but it did not work. What is the way to go for this?
In React, you don't directly manipulate DOM elements (well, almost never), including their the class lists. Instead, you keep your state information in the component and use that state information to render the elements that make up your component (including their classes). React will then compare the rendered elements you return with the DOM and make any necessary changes (such as updating the classList). So in your code, when you see that you have a correct matching, you wouldn't directly modify those DOM elements' classList lists, you'd update your state to remember the match, and use that state information in the next render to put the appropriate classes on the elements being rendered.
Here's a simpler example with a tickbox, but it's the same general concept:
const {useState} = React;
const Example = () => {
const [isChecked, setIsChecked] = useState(false);
return <div>
<label>
<input
type="checkbox"
checked={isChecked}
onChange={() => setIsChecked(flag => !flag)}
/>
Ticked
</label>
<div className={isChecked ? "yes" : "no"}>
Example
</div>
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.yes {
color: green;
}
.no {
color: #d00;
}
label {
user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Note how the state member isChecked determines what classes the div has, and is updated by ticking/unticking the checkbox.

Trigger animated div each time is called with React / Next.js

I'm new with React and Next.js, I'm creating a timeline, the timeline has colored bars which if pressed, the page shows a div below with info about each bar.
I managed to make the content below appear and disappear with useState hook so the content of each bar doesn't stack, I'm using an animated tag "Section" and only the first time I press any bar, the content is animated, the rest appears statically, I'm wondering if I can use something like the useEffect hook to maybe re-render each content div so the animation appears every time you click each bar, also to erase the last loaded div so doesn't stack on browser memory, I hope I explained myself I started with React 2 days ago, and thank you for your time.
Example reduced code:
//useState hook
const [content, setContent] = useState(null);
//Timeline section
<div>
<Bar1 onClick={() => setContent(BarContent_1)}/>
<Bar2 onClick={() => setContent(BarContent_2)}/>
</div>
//Content display
<div>
{content}
</div>
//Content of each bar, (<Section> div has the animation)
const BarContent_1 = () => {
return (
<Section>
Content of bar 1
</Section>
)
}
const BarContent_2 = () => {
return (
<Section>
Content of bar 2
</Section>
)
}
you can use useState for that to toggle classList on element here is example below what I do, in your project I don't know what you do but I will come up with card example first as your default value set state to false in card component and when you will click the button toggle that boolean false to true
like that () => setState(!state) it will always set the value of opposite state
when you will change state component always re-renders and checks the state, and you will make like that on more info div, if that state is true show more-info active that means your div will be displayed
and if it is false it will be dissapeared
const Card1 = () => {
const [showMore, setShowMore] = useState(false)
return (
<div className="card">
<div>Daniel</div>
<button onClick={() => setShowMore(!showMore)}>Show More</button>
<div class={showMore ? "more-info active": "more-info"}>This is more info Daniel</div>
</div>
)
}
also here is my css what I do
.more-info{
opacity: 0;
transition: 0.5s ease;
}
.active{
opacity: 1
}
in start thats good to make stuff like that but I would recommend you to use array objects for that to .map() components and make toggling animations on that, good luck
also quickly I made working example on sandbox
#callmenikk gave me an idea to use a conditional to render my styled div every time the condition met.
Solution:
//useState hook
const [content, setContent] = useState(null);
//Conditional function 'Refresh'
const Refresh = ({children}) => {
const active = content != setContent
if (active) {
return (
<Section>
{children}
</Section>
)
}
}
//Timeline section
<div>
<Bar1 onClick={() => setContent(BarContent_1)}/>
<Bar2 onClick={() => setContent(BarContent_2)}/>
</div>
//Content display and wrapping the content in my Refresh function
<Refresh>
{content}
</Refresh>
//Content of each bar, no need to wrap content in my styled (Section) tag since my Refresh button will
const BarContent_1 = () => {
return (
<div>
Content of bar 1
</div>
)
}
const BarContent_2 = () => {
return (
<div>
Content of bar 2
</div>
)
}

Display content while hiding other in React / Next.js

i just started with React and Next.js and I'm messing around with hooks and i got stuck, i created a vertical menu, i want to display the content below each title, but only one at a time taking in mind these 2 options:
if i click a title which content is already displayed, the content will just hide.
if i click a different title, the content of the last title will hide and the content of the new title will appear below it.
Thank you so much for your time.
I made a sandbox of the basic logic of my code:
https://codesandbox.io/s/affectionate-morning-tyj8j?file=/src/App.tsx
That same code is this:
import {useState} from "react";
export default function Test() {
const [content1, setContent1] = useState(null);
const [content2, setContent2] = useState(null);
const [content3, setContent3] = useState(null);
return (
<div>
<div onClick={() => setContent1(Content_1)}>
Click to show content 1
<div>
{content1}
</div>
</div>
<div onClick={() => setContent2(Content_2)}>
Click to show content 2
<div>
{content2}
</div>
</div>
<div onClick={() => setContent3(Content_3)}>
Click to show content 3
<div>
{content3}
</div>
</div>
</div>
)
}
const Content_1 = () => {
return (
<p>Content 1</p>
)
}
const Content_2 = () => {
return (
<p>Content 2</p>
)
}
const Content_3 = () => {
return (
<p>Content 3</p>
)
}
You should take a step back and think a bit more abstractly about what your UI interaction means.
if i click a title which content is already displayed, the content will just hide.
if i click a different title, the content of the last title will hide and the content of the new title will appear below it.
So effectively you only ever have one content section open/visible at-a-time. There is no need to store individual states for each content section you want to manually toggle, and as the number of content sections grows you'll need to increase number of toggles linearly. This is a terribly inefficient way to scale.
I suggest using a single state to hold which content section is active. Use a toggle handler to either toggle back "closed" a section if its id is set again, otherwise set a new section id. Conditionally render the section if its id matches the active id set in state.
function App() {
const [active, setActive] = useState<number>(-1);
const toggleHandler = (id: number) => () =>
setActive((active) => (active === id ? -1 : id));
return (
<div>
<div onClick={toggleHandler(1)}>
Click to show content 1{active === 1 && <Content1 />}
</div>
<div onClick={toggleHandler(2)}>
Click to show content 2{active === 2 && <Content2 />}
</div>
<div onClick={toggleHandler(3)}>
Click to show content 3{active === 3 && <Content2 />}
</div>
</div>
);
}
I used the same structure to be easier for you to understand :)
If you still have difficulties, keep me in touch.
Code suggested
import React, { useState } from 'react';
export default function Test() {
// states
const [content1, setContent1] = useState(true);
const [content2, setContent2] = useState(false);
const [content3, setContent3] = useState(false);
// content
const Content_1 = <p>Content 1</p>;
const Content_2 = <p>Content 2</p>;
const Content_3 = <p>Content 3</p>;
function setContent(id) {
setContent1(id === 1);
setContent2(id === 2);
setContent3(id === 3);
}
return (
<div>
<button onClick={() => setContent(1)}>Show 1</button>
<button onClick={() => setContent(2)}>Show 2</button>
<button onClick={() => setContent(3)}>Show 3</button>
{content1 && <div>{Content_1}</div>}
{content2 && <div>{Content_2}</div>}
{content3 && <div>{Content_3}</div>}
</div>
);
}

How to scroll 'onClick' component that is hidden before onClick event

When I click on the image I want that it is automatically scrolled to the component which is then displayed. I tried with anchor tags, but it's not working (I believe due to the fact that the component is hidden and at the same time when it is shown it should be scrolled to it ) , useRef - I get the error 'not defined' (I believe same reason as above).
Component is displyed onClick, but it does't scroll to the view-port of the user. Pls help, I'm out of the ideas :/
const WebContent = () => {
const [hidden, setHidden] = useState(false)
return (
<div>
<img onClick={() => setHidden(true)} src={first}/>
<div>
{hidden && <MyComponent/>}
</div>
</div>
)}
Your intuition is probably right that MyComponent is not yet mounted when you try to scroll to it. A simple way to do this would be to have MyComponent scroll itself into view when it mounts, if that's the behavior you're looking for.
const MyComponent = () => {
const ref = React.useRef(null);
useEffect(() => {
if (ref.current) ref.current.scrollIntoView();
}, [ref]);
return (
<div ref={ref}>
NOW YOU SEE ME
</div>
);
};
export default MyComponent;
One (hacky?) idea is add the ref to the surrounding div of the hidden content:
this.scrollHere = React.useRef(null);
...
return (
<div style={{ minHeight: 1 }} ref={this.scrollHere}>
{hidden && <div>My Hidden Component</div>}
</div>
)
Then you can run a function onClick, which sets hidden to true (which by the way is kinda irritating. Maybe just use "shown" as a quick improvement) and also lets the ref scrollIntoView:
const showAndScroll = () => {
setHidden(true);
this.scrollHere.current.scrollIntoView({
behavior: "smooth"
});
};
The minHeight has to be placed on the div since it is at height of 0 first and this messes with the scroll function (it scrolls below the hidden content).
See working example here.

Categories

Resources