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>
)
}
Related
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;
I am trying to make a hide/show navbar in ReactJS.
But on the first click I am getting this error and its working fine after the first click.
Note: Its twice and i have no idea why
Here is my code and its only working (after first error) when
setMenu(!menu);
is before
nav.classList.toggle("nav-open");
otherwise the error just keeps coming.
export default function Navbar() {
const [menu, setMenu] = useState(true);
const nav = document.querySelector("nav");
const openNav = () => {
setMenu(!menu);
nav.classList.toggle("nav-open");
};
return (
<nav id="nav-wrapper">
<header className="nav-header">
<div
className="arrow-btn"
onClick={() => {
openNav();
}}
>
{menu ? (
<Icon.HiChevronDoubleLeft size={20} className="arrows" />
) : (
<Icon.HiChevronDoubleRight size={20} className="arrows" />
)}
</div>
<img src={Profiledp} alt="." />
<p className="name">Naman Pokhriyal</p>
</header>
</nav>
This isn't the right way of doing this in React in the first place. You're already using the menu state value to determine if the menu is open is not, so why not continue to use the menu state value to determine if the menu is open or not? Something like this:
const openNav = () => {
setMenu(!menu);
};
return (
<nav id="nav-wrapper" className={menu ? "nav-open" : ""}>
// etc.
);
Basically any time you're trying to directly manipulate the DOM in React, take a step back and try to find a way to manage that via state instead. Direct DOM manipulation in React almost always leads to bugs unless you really know what you're doing under the hood in the framework.
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>
);
}
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.
I am trying to render an array of elements in react so that when you hover over an element it then filters the array of elements and re-renders with just the hovered element. Keeping position is buggy but not the worry for now.
It works but when the hover element is rendered it has no content
const [show, setShow] = useState(false);
const cards = [{key:"1",title:"elem1",content:"..."},{key:"2",title:"elem2",content:"..."},{key:"3",title:"elem3",content:"..."}]
const tempCards = []
My hover function returns
tempCards = [{key:"1",title:"elem1"}];
/// temp function allowing to work from button click
const changeShow = () => {
tempCards = cards.filter(item => item === cards[0]);
console.log(tempCards[0])
setShow(!show)
}
This is all working as it should but then when the element loads on the page no content shows
{show ? cards.map((item)=> {
return <CardSpinLeft
onClick={toggle}
key={item.key}
id={item.key}
image={item.img}
name={item.title}
title={item.title}
paragraph={item.content}
/>
}):
<CardSpinLeft
onClick={toggle}
key={tempCards.key}
id={tempCards.key}
image={tempCards.img}
name={tempCards.title}
title={tempCards.title}
paragraph={tempCards.content}
/>
}
not receiving any error's I even tried making the filter function async/await thinking its a loading issue. Only thing I can think is that react is pre-loading the content before it is there?