Tailwind CSS Dynamic State Updates But Not Rendered Correctly - javascript

My design has 3 divs. A left panel (filters), the main content which contains a cards displayed in a grid and a right panel (info). The side panels have a button which user can open and close like a drawer. I offset the main content by using tailwinds ml for margin-left relative to the left panels width and similar to right.
If panel is opened, extend width and update the grid's left offset with margin left. And vice versa.
Here is my code. The right panel works fine. But the left panel is problematic. By default the left panel is open. When I click on the button to close it via the callback method with toggleFilterVisibility the panel closes but the grid displays with no margin (which completely covers the left panel to re-open). I inspected the component's states and they are updating with the callback as expected. I also removed the ml-[${filterPanelWidth}px] with the string literals ml-[65px] and ml-[250px] which are rendered correctly.
I suspect I am implementing the dynamic render with tailwind incorrect or its rendering before waiting for the state updates somehow. console.log(filterPanelWidth) and clicking the button shows the correct state changes for both filterPanelWidth and filterPanelVisibility.
<div className={`bg-gray-100 pt-14 ml-[${filterPanelWidth}px]`}>
import FilterPanel from "../components/FilterSidePanel";
import InfoPanel from "../components/InfoSidePanel";
import CardGridPanel from "../components/CardGridPanel";
const HomeRoute: React.FC = () => {
const [filterPanelWidth, setFilterPanelWidth] = useState(250);
const [isFilterPanelVisible, setFilterPanelVisibility] = useState(true);
const [infoPanelWidth, setInfoPanelWidth] = useState(45);
const [isInfoPanelVisible, setInfoPanelVisiblity] = useState(false);
const toggleFilterVisibility = () => {
if (isFilterPanelVisible) {
setFilterPanelWidth(65);
} else {
setFilterPanelWidth(250);
}
setFilterPanelVisibility(!isFilterPanelVisible);
};
const toggleInfoVisibility = () => {
if (isInfoPanelVisible) {
setInfoPanelWidth(45);
} else {
setInfoPanelWidth(600);
}
setInfoPanelVisiblity(!isInfoPanelVisible);
};
return (
<>
<div className="main-content w-full bg-gray-100">
<div className="flex">
<div className="left-sidepanel-content bg-white fixed h-screen">
<FilterPanel toggleVisibility={toggleFilterVisibility} isVisible={isFilterPanelVisible}/>
</div>
<div className={`bg-gray-100 pt-14 ml-[${filterPanelWidth}px]`}>
<CardGridPanel />
</div>
<div className={`right-sidepanel-content right-0 bg-white w-[${infoPanelWidth}px] fixed h-screen`}>
<InfoPanel toggleVisibility={toggleInfoVisibility} isVisible={isInfoPanelVisible}/>
</div>
</div>
</div>
</>
);
};
export default HomeRoute;
Update 1: After looking at the docs I see I tried to apply the dynamic rendering for tailwind like so:
<div className="bg-gray-100 pt-14 {{ isFilterPanelVisible ? 'ml-[250px]' : 'ml-[65px]' }}">
<CardGridPanel />
</div>
but when inspecting the elements I see the following literal string. How do I resolve this? I also tried to replace the quotes with backticks

You can't make dynamic class names like that in Tailwind. The classes must be compile-time constants. Instead of doing ml-[${filterPanelWidth}px] you must do something like isFilterPanelVisible ? 'ml-[65px]' : 'ml-[250px]'. You can read more about it in the Tailwind docs.

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;

First click on Nav Menu Button Giving Error and working fine after that ReactJs

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.

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>
)
}

How to display a header only after the user starts to scroll down? [React]

I'm using react, and I would like to display a header only after the user has scrolled down 150px on the page.
So when the user starts to scroll down, I would like a sticky header to appear. And again, when the user scrolls to the top of the page, the sticky header disappears.
<div className="container">
// display this on top of the page:
<JumbotronImage />
// display this when user starts to scroll, sticky on top of the page
<StickyHeader />
</div>
I tried to do it with window.addEventListener('scroll'... , and I also tried https://github.com/fisshy/react-scroll but couldn't get it to work yet. Any suggestions?
I can think upon that your code would look as following. And the solution should fit like this.
export class App extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this.stickyHeader).addEventListener('scroll', this.handleScroll.bind(this));
}
componentWillUnmount() {
ReactDOM.findDOMNode(this.stickyHeader).removeEventListener('scroll', this.handleScroll.bind(this));
}
handleScroll() {
// Add the logic here
}
render() {
const {props} = this;
return (
<div className="container">
// display this on top of the page:
<JumbotronImage />
// display this when user starts to scroll, sticky on top of the page
<StickyHeader ref = {ele => this.stickyHeader = ele} />
</div>
);
}
}
You can refer this article which worked for me, http://blog.sodhanalibrary.com/2016/07/detect-scroll-direction-using-reactjs.html#.Wo0hq0nTS-4 to add logic inside handleScroll.
I generally use this script for sticky header for my header with id="sticky"
$(window).scroll(function () {
var iCurScrollPos = $(this).scrollTop();
if (iCurScrollPos) {
//#sticky put your element for which you want it to apply
$('#sticky').addClass('stickyMenu');
} else {
$('#sticky').removeClass('stickyMenu');
}
});
Add css for attached class

Hide and Show Looped Components in React

I have a series of questions for a sign-up flow I am building. Currently, I am looping through each components and displaying them all on one page. My questions are, How do I show only one at a time? How can I include a slide left transition/animation when each slide hides/shows? I would like each question to display individually then once the user clicks next, it hides the first question and displays the second. I am a bit newer to React so I apologize if this is a basic question but I cannot figure it out.
Below are breakouts of my code:
import React from 'react';
import Q1Name from './questions/Q1Name';
import Q2Birthday from './questions/Q2Birthday';
import Q3City from './questions/Q3City';
import Q4YouReady from './questions/Q4YouReady';
import Q5Setting from './questions/Q5Setting';
import Q6Length from './questions/Q6Length';
import Q7Email from './questions/Q7Email';
class SignUpPage extends React.Component {
render() {
const components = [Q1Name, Q2Birthday, Q3City, Q5Setting, Q6Length, Q7Email];
const componentsToRender = components.map((Component, i) => (
<Component key={i} />
));
return (
<div className = "container-fluid">
<div className = "question-box">
{componentsToRender}
</div>
</div>
);
}
}
export default SignUpPage;
This is an example component - they are all slightly different so I am showing the two primary types:
the first only has a single "next button"
import React from 'react';
class Q2Birthday extends React.Component {
render() {
return (
<div className="questions">
<h1 id="question-h1">When is your birthday?</h1>
<form>
<div className="form-group">
<input type="date" className="form-control custom-form" id="birthdayInput" aria-describedby="birthday" placeholder="" />
</div>
<button type="submit" className="btn btn-custom btn-lg">Next Question!</button>
</form>
</div>
);
}
}
export default Q2Birthday;
the second has 3 button options the user can select from
import React from 'react';
class Q6Length extends React.Component {
render() {
return (
<div className="questions">
<h1 id="question-h1">How long would you like your trip to be?</h1>
<button type="submit" className="btn btn-custom-select btn-lg">Just a weekend!</button>
<button type="submit" className="btn btn-custom-select btn-lg">A full week!</button>
<button type="submit" className="btn btn-custom-select btn-lg">I'm flexible!</button>
</div>
);
}
}
export default Q6Length;
I also want to add in a slide left transition for the "questions" div within the question-box class. I have been reading up on react-transition-group but I am a bit confused on how to implement it. Also, with this application, I do not need to store the values of the form data.
How do I show only one at a time?
Given that you want to do a slide transition between them, you need at least the one being left behind and the next one to show to be in the DOM at switchover time. When not switching it's possible to have only the current one in the DOM. But simplest would probably be to always have all of them in the DOM, just with the previous/next ones out of the left/right sides of the viewport.
So to answer the question of how to show just one at a time, one way would be to translate all "old" ones left by 100% of the container width, leave the current one be, and translate all "next" ones right by 100%.
Styles for that might look like this:
const oldStyle = {
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
transform: 'translate(-100%)',
};
const currentStyle = {
position: 'relative',
transform: 'translate(0)',
};
const nextStyle = {
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
transform: 'translate(100%)',
};
Depending on where this is on your page, you might need some additional styles to hide the overflowing next/previous slides. Look up the overflow property. You may also need to fix heights or widths of the container -- look into this if you find the non-current slides have an unexpected (such as zero) height or width.
To apply the appropriate styles to each panel, you'll need to know which is which.
I'd suggest keeping track of the current slide index in your parent component's state. Say you have this in this.state.currentSlide. With that, you can choose the slide styles like this:
const componentsToRender = components.map((Component, i) => (
<Component key={i} style={i < this.state.currentSlide ? oldStyle : i === this.state.currentSlide ? currentStyle : nextStyle} />
));
In order for that style prop to pass through to your slides, you'd need to tweak the slides a little. The simplest way would just be to explicitly pass that one through:
<div className="questions" style={this.props.style}>
But how do we set the current slide in state, and keep it up to date? Well, in the simplest case, you need to initialize it at zero. Your slide components will need to tell the parent when they've finished. And you'll need to notice that, and update the state.
class SignUpPage extends React.Component {
constructor(props) {
super(props);
// Set initial state
this.state = {
currentSlide: 0,
};
}
// Handle notification from a child slide that we should move to the next
nextSlide() {
this.setState({
currentSlide: this.state.currentSlide + 1,
});
}
render() {
...
const componentsToRender = components.map((Component, i) => (
<Component key={i} style={this.props.style} onNext={this.nextSlide.bind(this)} />
));
The child components then need to call this method which has been passed in when they're finished:
class Q2Birthday extends React.Component {
handleSubmit(event) {
// Don't perform an actual form submission
event.preventDefault();
// Notify the parent
this.props.onNext();
}
render() {
return (
<div className="questions" style={this.props.style}>
<h1 id="question-h1">When is your birthday?</h1>
<form onSubmit={this.handleSubmit}>
...
How can I include a slide left transition/animation when each slide hides/shows?
With the above styles, it might be as simple as setting the following style for each of the slides:
transition: transform 0.5s;

Categories

Resources