How can I reapply style classes that get removed by ThemeProvider? - javascript

I'm trying to make a SPA with react and using styled-component for styling and theme changing. The problem i have when i add classes by Javascript and then try to change theme all the classes added by JavaScript are removed.
Can someone tell me what is the reason for this and how can i solve this problem?
Here is the example, where you'll see that when you scroll down on i add class to header with help of JavaScript to give bottom border but when i toggle theme with button then added class is removed.
const themeToggler = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
const StyleHeader = styled.header `
background: ${(props) => props.theme.body};
`;
function scrollPos(maxScrollPos) {
let scrollPos = document.documentElement.scrollTop;
if (scrollPos >= maxScrollPos) {
addClassToElement("glow-shadow", ".header");
} else {
removeClassFromElement("glow-shadow", ".header");
}
}
<ThemeProvider theme={theme==="light" ? light : dark}>
<GlobalStyles />
<StyleHeader className={`header pt-3 pb-3 fixed-top`}>
<button onClick={themeToggler}>Toggle</button>
</StyleHeader>
<div className="content">
scrool down to see border on header then click the toggle button to change the theme and border class will be removed
<h1>h1</h1>
</div>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
</ThemeProvider>

You need to run your scrollPos function after theme change. The theme switcher apparently removes all but the original classes on the element, so you need to replace them. Something like this, where we watch the theme property for changes:
import React, { useEffect } from "react";
export default function App() {
...
useEffect(() => {
scrollPos(100);
}, [theme, scrollPos]);
...
};
Demo

I accepted the answer from #isherwood but i found another way so i would like to share it.
import React, { useState} from "react";
export default function App() {
...
const [isScrolled, setIsScrolled] = useState(false);
function scrollPos(maxScrollPos) {
let scrollPos = document.documentElement.scrollTop;
if (scrollPos >= maxScrollPos) {
setIsScrolled(true);
} else {
setIsScrolled(false);
}
}
window.addEventListener("scroll", (event) => {
scrollPos(100);
});
return (
<ThemeProvider theme={theme==="light" ? light : dark}>
<GlobalStyles />
<StyleHeader className={`nq-header pt-3 pb-3 fixed-top ${
isScrolled ? "glow-shadow" : ""
}`}>
<button onClick={themeToggler}>Toggle</button>
</StyleHeader>
<div className="content">
scrool down to see border on header then click the toggle button to change the theme and border class will be removed
<h1>h1</h1>
</div>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
<h1 className="content">h1</h1>
</ThemeProvider>
)};

Related

I would like to change only clicked header link text color with react

I started to learn to code recently and to create my portfolio. I am working on the header and I would like to change the text color header link tag when it is only clicked once.
When I clicked the link, the text color changed to red and when I clicked the link now but when I click another link, the previous link is still red.
I would like to change the text color back from red to gray when I click another.
Example:- OK - home about work skill contact NO - home about work skill contact
Header.jsx
import React, { useState } from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage } = props;
const [onClickChangeColor, setOnClickChangeColor] = useState(false);
const redText = () => {
setOnClickChangeColor(!onClickChangeColor);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={onClickChangeColor ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
HeaderLayout.jsx
import React from "react";
import Header from "./Header";
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header headerPage={headerPage}>{headerPage}</Header>
))}
<div
className="resume"
class="w-24 h-8 text-[#FF5757] border border-[#FF5757] rounded-md"
>
<a
href="https://drive.google.com/file/d/usp=sharing"
class="block"
>
resume
</a>
</div>
</div>
);
};
export default HeaderLayout;
I am using React and Tailwind. English is my second language and I hope everyone understands what I want to do. Thank you in advance.
change the color handling state to parent (Header Layout)
const HeaderLayout = () => {
const headerPages = ["home", "about", "work", "skill", "contact"];
const [selectedHeader, changeSelectedHeader] = useState("");
return (
<div class="sticky top-0 flex justify-end h-20 text-md pt-10 mr-10 text-center bg-[#292929]">
{headerPages.map((headerPage) => (
<Header
headerPage={headerPage}
changeSelectedHeader={changeSelectedHeader}
selectedHeader={selectedHeader}
>
{headerPage}
</Header>
))}
then, make the child like this
import React from "react";
import { Link as ScrollLink } from "react-scroll";
const Header = (props) => {
const { headerPage, changeSelectedHeader, selectedHeader } = props;
const redText = () => {
changeSelectedHeader(headerPage);
};
return (
<div class="mr-8 cursor-pointer">
<ScrollLink
className={headerPage === selectedHeader ? "text-[#FF5757]" : ""}
onClick={redText}
to={headerPage}
smooth={true}
duration={500}
>
{headerPage}
</ScrollLink>
</div>
);
};
export default Header;
You can track the selected item index in the HeaderLayout component and then use it to decide the header item color depending on that.
Add another state to maintain the selected header item state in HeaderLayout.
const [selectedIndex, setSelectedIndex] = useState(-1);
Provide the headerIndex, selectedIndex, and setSelectedIndex as props to the Header compoenent.
{headerPages.map((headerPage, headerIndex) => (
<Header
headerPage={headerPage}
setSelectedIndex={setSelectedIndex}
selectedIndex={selectedIndex}
headerIndex={headerIndex}
>
{headerPage}
</Header>
))}
In the Header component get the props and set the header item CSS as below.
const { headerPage, selectedIndex, setSelectedIndex, headerIndex } = props;
...
...
<ScrollLink
className={headerIndex === selectedIndex ? "text-[#FF5757]" : ""}
onClick={() => setSelectedIndex(headerIndex)}
to={headerPage}
smooth={true}
duration={500}
>
Working Demo

I have side navigation and when I scroll down to pick a certain page, the navigation bar jumps to the top of the page

I have been looking for the solution here on stackoverflow, but couldn't find any correct way to solve the issue.
My issue:
I have side navigation bar on the left, when i scroll down and select certain page the navigation bar jumps to the top of the navigation bar. I want navigation bar to remain on the same position when I clicked. Pls advise.
HERE IS THE CODE:
const LayoutWithSidebar = ({ toggleSideBar, setToggleSidebar, btn }) => {
const { t } = useTranslation();
const handleEnterMouse = () => {
setToggleSidebar(true);
};
return (
<aside
className={`left-sidebar-style ${
toggleSideBar ? 'open-left-sidebar' : 'left-sidebar'
}`}
onMouseEnter={handleEnterMouse}
data-sidebarbg="skin5">
<div className="scroll-sidebar">
<UserProfile btn={btn} toggleSideBar={toggleSideBar} translation={t} />
<SidebarLinks btn={btn} toggleSideBar={toggleSideBar} translation={t} />
</div>
{(toggleSideBar || btn) && <SidebarFooter />}
<style jsx toggleSideBar={toggleSideBar}>
{Styles}
</style>
</aside>
);
};
export default LayoutWithSidebar;
SideBarLinks.js file
const SidebarLinks = (props) => {
return (
<nav className="sidebar-nav fixed">
<ul id="sidebarnav" className="in">
{navItems.map((navItem) => (
<SidebarItem key={navItem.transKey} {...navItem} {...props} />
))}
</ul>
</nav>
);
};
export default SidebarLinks;
Thank you.

React Bootstrap scroll down to content after accordion is opened

I have a react bootstrap table column accordion that has content in it. I am facing the issue is if panel opens user unable to identify the content until scroll down.
I wanted to be able to scroll down to the content when accordion collapse is opened.
This is what my parent function component looks like,
import React, {forwardRef, useContext} from "react";
import AccordionContext from "react-bootstrap/AccordionContext";
import { Table } from "react-bootstrap";
function CustomToggle({ children, eventKey, callback, className }) {
const currentEventKey = useContext(AccordionContext);
const decoratedOnClick = useAccordionToggle(
eventKey,
() => {
let eventType = `${currentEventKey ? 'close' : 'open'}`;
callback && callback(eventKey,eventType)
},
);
return (
<tr onClick={decoratedOnClick}>
<td><Image src={"accordion_arrow_down.svg"} /></td>
</tr>
)
}
function ParentComponent()
{
const scrollDownRef = React.useRef(null);
const toggleHandle = (eventKey,eventType) => {
if(scrollDownRef && scrollDownRef.current) {
scrollDownRef.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "start"
});
}
}
return (
<div>
<Table>
<tbody>
<CustomToggle callback={toggleHandle} eventKey={{}} />
<tr>
<td>
<Accordion.Collapse eventKey={{}}>
<ChildComponent ref={scrollDownRef} />
</Accordion.Collapse>
</td>
</tr>
</tbody>
</Table>
</div>
)
}
and child component looks like below,
function ChildComponent({ref}) {
return (
<div>
<Form>
<div ref={ref}>
</div>
</Form>
</div>
)
}
export default forwardRef(ChildComponent)
I have also tried with setTimeout function as well, if the ref element has to be identified if there is any delay with the accordion open time. Nothing worked here.
I have tried other scroll methods in the place of scrollIntoView funtion. But neither do anything to the page.
Any help would be appreciated!
I have tried to use your code but it contains many errors here and there. So I have created a simple example that you can use.
In App.js
import React, { useState, forwardRef, useRef } from "react";
import { Accordion, Table, Card, Container } from "react-bootstrap";
const ChildComponent = forwardRef((props, ref) => {
return (
<div>
<Accordion.Collapse eventKey="0" className="border">
<Card.Body
style={{ height: "calc(100vh + 200px)" }}
className="d-flex align-items-end justify-content-center"
>
<Container>
<div ref={ref}> Some Content. Lorem Ipsum. Hello World.</div>
</Container>
</Card.Body>
</Accordion.Collapse>
</div>
);
});
const App = () => {
const [activeEventKey, setActiveEventKey] = useState("");
const accordElem = useRef(null);
const handleClickToggle = (eventKey) => {
if (eventKey === activeEventKey) {
setActiveEventKey("");
} else {
setActiveEventKey(eventKey);
//Handle Scroll here when opening
setTimeout(() => {
accordElem.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest",
});
}, 400);
}
};
return (
<div>
<Accordion activeKey={activeEventKey}>
<Table>
<tbody>
<tr>
<Accordion.Toggle
as="td"
eventKey="0"
onClick={() => handleClickToggle("0")}
>
Add Mock
</Accordion.Toggle>
</tr>
<tr>
<td>
<ChildComponent ref={accordElem} />
</td>
</tr>
</tbody>
</Table>
</Accordion>
</div>
);
};
export default App;
Explanation:
I used Card, Container components for the purpose of demonstration. I added some styles as well to make sure accordion content is taller than my screen size so scrolling can be seen
Instead of Custom Toggle, you can use Accordion.Toggle and give the value as='td' for it to be used as a element.
You can use setTimeout so that component is rendered and ref is not null
Here is a CodeSandbox link with example : https://codesandbox.io/s/festive-sun-o0e4ih

Remove border/style from previously clicked link - React/Nextjs

I have a function that triggers onClick which adds borders to the clicked element.
Here is the code in the component:
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Logo from '../../components/logo.svg';
import React from 'react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
const {
publicRuntimeConfig: { domain },
} = getConfig();
export default function MediaNav() {
// If router equals link in list, show blue border
const router = useRouter();
const menu = ['Home', 'FAQs', 'Contact Us', 'Social Media'];
const [selectedPage, setPage] = useState({
borderColor: '',
});
const handleLinkClick = e => {
setPage({
borderStyle: '3px solid #005ba9',
});
e.target.style.borderLeft = selectedPage.borderStyle;
e.target.style.borderRight = selectedPage.borderStyle;
};
return (
<nav>
<div>
<Link href={`${domain}`}>
<a>
<Logo />
</a>
</Link>
</div>
<ul
>
<div>
<ul>
<li
>
All Sites
</li>
</ul>
<ul
>
{menu.map((item, i) => {
return (
<li key={i}>
<Link href={item === 'Home' ? '/subdomain/link' : `/subdomain/${item}`.replace(/\s+/g, '').toLowerCase()}>
<a onClick={handleLinkClick}>
{item}
</a>
</Link>
</li>
);
})}
</ul>
) : null}
</div>
</ul>
</nav>
);
}
At the moment, when the element in the link list is clicked, the blue border is being applied, however, if I click another link, I wanted the previously clicked element link to be removed its borders.
As this is NextJs and I have a Link tag wrapping up the link element, loading is not occurring, therefore I don't know how to make a difference between previously clicked element and currently clicked element.
Any idea how to remove the borders to already clicked link when next link is clicked?
I think a better approach to this problem would be having a state to save currentPage user visiting and depending on currentPage state giving a style to a element.
import { useEffect, useState } from 'react';
import Link from 'next/link';
import Logo from '../../components/logo.svg';
import React from 'react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
const {
publicRuntimeConfig: { domain },
} = getConfig();
export default function MediaNav() {
// If router equals link in list, show blue border
const router = useRouter();
const menu = ['Home', 'FAQs', 'Contact Us', 'Social Media'];
const [currentPage, setCurrentPage] = useState('')
const handleLinkClick = pageName => {
setCurrentPage(pageName);
};
return (
<nav>
<div>
<Link href={`${domain}`}>
<a>
<Logo />
</a>
</Link>
</div>
<ul
>
<div>
<ul>
<li
>
All Sites
</li>
</ul>
<ul>
{menu.map((item, i) => {
return (
<li key={i}>
<Link href={item === 'Home' ? '/subdomain/link' : `/subdomain/${item}`.replace(/\s+/g, '').toLowerCase()}>
<a onClick={() => handleLinkClick(item)} style={{ border: currentPage === item ? '3px solid #005ba9': 'initial' }}>
{item}
</a>
</Link>
</li>
);
})}
</ul>
</div>
</ul>
</nav>
);
}
I think instead of adding a style on a target element, you can try adding a class, let's say highlight on the element.
highlight class would look like -
.highlight {
border-left: '3px solid #005ba9',
border-right: '3px solid #005ba9'
}
So only the elements which have this class would show the border styles applied. Since the components would re-render on state changes, make sure you are applying the highlight class on the link in map function logic. This would ensure other elements won't have this class and this would solve the problem.
PS: Removing of the previously applied styles would be tedious. I think the above solution would work without much hassle and it would work great.

How to get DOM element within React component?

I'm rendering multiple of the same component, each with their own tooltip. Can I write code that will only look within the HTML of each component, so I'm not affecting all the other tooltips with the same class name? I'm using stateless components. Here is the code:
OptionsComponent.js:
import React from 'react';
const OptionsComponent = () => {
const toggleTooltip = event => {
document.getElementsByClassName('listings-table-options-tooltip').classList.toggle('tooltip-hide');
event.stopPropagation();
};
return (
<div className="inline-block">
<span onClick={toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
</div>
);
};
Backbone.js has something like this, allowing you to scope your document query to begin within the view element (analogous to a React component).
With React, you don't want to modify the DOM. You just re-render your component with new state whenever something happens. In your case, since you want the OptionsComponent to track its own tooltip state, it really isn't even stateless. It is stateful, so make it a component.
It would look something like this:
class OptionsComponent extends React.Component {
state = {
hide: false
};
toggleTooltip = (ev) => this.setState({ hide: !this.state.hide });
render() {
const ttShowHide = this.state.hide ? "tooltip-hide" : "";
const ttClass = `tooltip listings-table-options-tooltip ${ttShowHide}`;
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
<div className={ttClass}>
Tooltip content
</div>
</div>
);
// Alternatively, instead of toggling the tooltip show/hide, just don't render it!
return (
<div className="inline-block">
<span onClick={this.toggleTooltip} className="icon icon-options listings-table-options-icon"> </span>
{/* do not render the tooltip if hide is true */}
{!this.state.hide &&
<div className="tooltip listings-table-options-tooltip">
Tooltip content
</div>
}
</div>
);
}
}
You should use refs.
Slightly modified from React docs:
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.focus = this.focus.bind(this);
}
focus() {
var underlyingDOMNode = this.textInput; // This is your DOM element
underlyingDOMNode.focus();
}
render() {
// Use the `ref` callback to store a reference to the text input DOM
// element in this.textInput.
return (
<div>
<input
type="text"
ref={(input) => this.textInput = input} />
<input
type="button"
value="Focus the text input"
onClick={this.focus}
/>
</div>
);
}
}
A comfortable approach would be modifying your toggleTooltip method this way:
...
const toggleTooltip = event => {
event.target.parentNode.querySelector('.tooltip').classList.toggle('tooltip-hide');
};
...
I would however recommend having a state to represent the tooltip displaying or not.
With https://github.com/fckt/react-layer-stack you can do alike:
import React, { Component } from 'react';
import { Layer, LayerContext } from 'react-layer-stack';
import FixedLayer from './demo/components/FixedLayer';
class Demo extends Component {
render() {
return (
<div>
<Layer id="lightbox2">{ (_, content) =>
<FixedLayer style={ { marginRight: '15px', marginBottom: '15px' } }>
{ content }
</FixedLayer>
}</Layer>
<LayerContext id="lightbox2">{({ showMe, hideMe }) => (
<button onMouseLeave={ hideMe } onMouseMove={ ({ pageX, pageY }) => {
showMe(
<div style={{
left: pageX, top: pageY + 20, position: "absolute",
padding: '10px',
background: 'rgba(0,0,0,0.7)', color: '#fff', borderRadius: '5px',
boxShadow: '0px 0px 50px 0px rgba(0,0,0,0.60)'}}>
“There has to be message triage. If you say three things, you don’t say anything.”
</div>)
}}>Yet another button. Move your pointer to it.</button> )}
</LayerContext>
</div>
)
}
}

Categories

Resources