Adding/removing a class on a ReactJS component when a user scrolls - javascript

I want to change a class on my navigation bar, so when a user scrolls it fades from transparent to opaque. From following some other questions on here and other solutions online i've come up with the below and tweaked it a few ways, but nothing seems to be working.
I added in a console log to error check but it never runs.
Page component
import React from 'react'
import Nav from './_includes/Nav.jsx'
import VideoBanner from './_includes/VideoBanner.jsx'
import Section from './_includes/Section.jsx'
import { object } from 'prop-types'
class Homepage extends React.Component {
constructor (props) {
super(props)
if (props.initialSetup) props.initialSetup()
this.state = {
navOpaque: true
}
this.handleScroll=this.handleScroll.bind(this)
}
componentDidMount () {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount () {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll () {
const { navOpaque } = this.state
const { pageYOffset } = window;
if (pageYOffset >= 10 ) {
this.setState ({
navOpaque: false
})
}
console.log('you have scrolled')
return navOpaque
}
render () {
const { navOpaque } = this.state
return (
<div className="homepageContainer">
<Nav
navOpaque={navOpaque}
/>
<VideoBanner />
<Section />
</div>
)
}
}
Homepage.propTypes = {
dataSource: object,
domainSettings: object,
pageData: object.isRequired,
}
export default Homepage
Nav component
import React from 'react'
import NavItem from './NavItem.jsx'
import { bool } from 'prop-types'
import classnames from 'classnames'
class Nav extends React.Component {
constructor (props) {
super(props)
this.state = {
activeTab: '',
highlight: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick (activeTab) {
if (!activeTab) activeTab = ''
this.setState({
activeTab
})
}
render() {
const { highlight } = this.state
const { navOpaque } = this.props
const navClass = classnames({
'opaque': navOpaque,
'navbar': true,
'navbar-default': true,
'navbar-fixed-top': true,
'hidden-print': true,
'navbar-megamenu': true
})
return (
<nav id='header' className={navClass} role='navigation'>
<div className='container'>
<div className='navbar-header'>
<button type='button' className='navbar-toggle hidden-sm hidden-md hidden-lg'>
<span className='sr-only'>Toggle navigation</span>
<span className='icon-bar' />
<span className='icon-bar' />
<span className='icon-bar' />
</button>
<a className='navbar-brand' href='/'>Brand</a>
</div>
<div className='col-md-4 collapse navbar-collapse'>
<ul className='nav navbar-nav list-inline'>
{Object.keys(navConfig).map(function (listGroup, key) {
return(
<NavItem
key={key}
listGroup={listGroup}
linkData={navConfig[listGroup]}
highlight={ navConfig[listGroup].text === 'test' ? {highlight} : null }
/>
)
})}
</ul>
</div>
</div>
</nav>
)
}
}
Nav.propTypes = {
navOpaque: bool
}
export default Nav

Create a class' attribute ref with React.CreateRef() and attach it to the element where you want to remove classes on scroll.
Then in your handleScroll method, take this.ref.current
and do whatever you want on the classList
this.ref.current.classList.remove('my-class')
Hope this help.
https://reactjs.org/docs/refs-and-the-dom.html
https://www.w3schools.com/jsref/prop_element_classlist.asp

Your code seems to be right, at least to have the console.log to be executed when you scroll.
You might try to check if your class container has a height that allows you to scroll.
Check if this example helps:
https://codesandbox.io/s/nwr99l5l8p
Note that in the styles.css, the container has a height of 200vh, allowing the scrolling.
.App {
font-family: sans-serif;
text-align: center;
height: 200vh;
}

Related

GlideJs adding buttons

I created a glider that works good, no problem with the functionality, what I'm trying to achieve is to add a button inside the slides. So far, the links and all the slide content works good but not the button click event. I tried adding the .disable() and the pause() methods but it doesn't work. And I can't find anything like this, nor anything in the documentation. If anyone would have an approach, it'll help me a lot.
Glide holder:
import React, { Component } from 'react';
import Glide, {Swipe, Controls} from '#glidejs/glide';
import myComponent from './myComponent';
class myHolder extends Component {
constructor(props) {
super(props);
}
}
componentDidMount() {
const glide = new Glide(`.myGliderComponent`, {
type: carouselType,
focusAt: 0,
perTouch: 1,
perView: 4,
touchRatio: 1,
startAt: 0,
rewind: false,
});
glide.mount({Controls});
const CAROUSEL_NUMBER = 3;
const carouselType = this.props.displayedProducts.length <= CAROUSEL_NUMBER ? 'slider' : 'carousel';
}
render() {
return (
<div>
<div data-glide-el="track" className="glide__track">
<ul className="glide__slides">
{
this.props.displayedProducts.map(({ name, image,} = product, index) => (
<li className="glide__slide slider__frame">
<MyComponent
name={name}
image={image}
/>
</li>
))
}
</ul>
</div>
</div>
);
}
}
export default myHolder;
myComponent:
import React from 'react';
const myComponent = (
{
name,
image,
}
) => {
const buttonClicked = () => {
console.log("button clicked")
}
return (
<div>
<p>{name}</p>
<img
alt=""
src={image}
/>
<button onClick={buttonClicked}>Testing btn</button>
</div>
);
}
export default myComponent;
For anyone trying the same, I just added position:static to the class, and the glide.mount({Anchor}); to the mount() method

Creating re-orderable card component in React

I'm currently working my way through React, and I'm building a portfolio site. On the work page, I have 3 tiles, which will eventually be overlapping. When a card is clicked, it opens a side panel, and (hopefully) the active card goes to the front, pushing the others behind it to fill in the space.
I currently have the cards, or tabs working, I just need help with how to rearrange them on click.
This is the Tab.js component
import PropTypes from 'prop-types';
class Tab extends Component {
onClick = () => {
const { label, onClick } = this.props;
console.log('that tickles', {onClick})
onClick(label);
}
render() {
const {
onClick,
props: {
activeTab,
label,
position,
inactivePosition,
style
},
} = this;
console.log(this)
let className = 'tab-list-item';
if (activeTab === label) {
className += ' tab-list-active';
}
return (
<div
style={style}
className={className}
onClick={onClick}
>
{label}
</div>
);
}
}
export default Tab;
The tabs.js component that brings things together
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './tabstyle.css'
import Tab from './tab.js';
class Tabs extends Component {
static propTypes = {
children: PropTypes.instanceOf(Array).isRequired,
}
constructor(props) {
super(props);
this.state = {
activeTab: this.props.children[0].props.label,
};
}
onClickTabItem = (tab) => {
this.setState({
activeTab: tab
});
}
render() {
const {
onClickTabItem,
props: {
children,
position,
},
state: {
activeTab,
inactiveTab1,
inactiveTab2,
}
} = this;
return (
<React.Fragment>
{children.map((child) => {
const { label } = child.props;
return (
<Tab
activeTab={activeTab}
key={label}
label={label}
onClick={onClickTabItem}
/>
);
})}
<div className="tab-content">
{children.map((child) => {
if (child.props.label !== activeTab) return undefined;
return child.props.children;
})}
</div>
</React.Fragment>
);
}
}
export default Tabs;
the actual work page
export default function Work() {
return (
<React.Fragment >
<Tabs>
<div label="Example1" position='1'>
what in the world
</div>
<div label="Example2" position='2'>
lorem ipsum
</div>
<div label="Example3" position='3'>
Nothing to see here, this tab is!
</div>
</Tabs>
</React.Fragment>
);
}
}
.tab-list-item {
background-color:#fff;
position: relative;
transition: all .5s;
}
.tab-content {
color: #fff;
background-color: rgb(19, 47, 74);
grid-area:1/13/span 9/ span 8;
z-index: 5;
padding:3em;
}
.tab-list-active {
background-color: #f0f;
grid-area: 2/3/span 4/ span 4;
}
.position2{
grid-area:3/4/span 4/ span 4;
}
.position3{
grid-area:4/5/span 4/ span 4;
}
In my mind, I pictured building an array, and simply having the first card be active, then when another card is clicked, that card is sent to position 0, and so on.
I hope that's everything. As I said, I'm pretty new to React, bit less new to vanilla JavaScript, but still pretty green. Any guidance would be massively appreciated, as I'm at a bit of an impasse at the moment.
For a working demo, I'm using AWS Amplify to host it temporarily https://master.d2wqg4b36m462q.amplifyapp.com/work

How do you use multiple react components on the same element?

I would like to hover a div, get the image inside that div, show it and then make the image follow my cursor. The hover component is working but then I can't make the follow cursor component work. How would you go about doing this ?
Component number 1 :
import React from 'react';
class FollowMouse extends React.Component {
state = {
xPos: 0,
yPos: 0
};
onMouseMove(e) {
this.setState({
xPos: e.screenX,
yPos: e.screenY
});
}
render() {
return (
<div
onMouseMove={this.onMouseMove.bind(this)}
className="img-ctn"
>
{this.props.children(this.state.xPos, this.state.yPos)}
</div >
);
}
}
export default FollowMouse;
Component 2 :
import React from 'react';
class HoverProject extends React.Component {
state = {
isHovered: false,
};
onMouseEnter() {
this.setState({ isHovered: true });
}
onMouseLeave() {
this.setState({ isHovered: false });
}
render() {
return (
<div
onMouseEnter={this.onMouseEnter.bind(this)}
onMouseLeave={this.onMouseLeave.bind(this)}
className="project-item"
>
{this.props.children(this.state.isHovered)}
</div >
);
}
}
export default HoverProject;
and then the parent component.
import React from 'react';
// modules
import HoverProject from '../modules/HoverProject';
import FollowMouse from '../modules/FollowMouse';
import VLEC from '../images/vlec.png';
class ProjectList extends React.Component {
constructor(props) {
super(props);
this.sels = {
state: 'active'
};
};
render() {
return (
<div className="project-list module">
<div className="sectionTitle">Project I've worked on</div>
{this.props.data.map((res, i) => (
<HoverProject key={i}>
{
isHovered =>
<div className="inner-ctn">
{/* <FollowMouse /> */}
<img className={"project-image " + (isHovered ? this.sels.state : "")} src={VLEC} alt="VLEC" />
<div className="header">
<div className="number">0{res.id + 1}</div>
<div className="name">{res.nomProjet}</div>
</div>
<div className="item-ctn">
<div className="categ">{res.categProjet}</div>
<div className="roles">{res.roles}</div>
<div className="date">{res.date}</div>
</div>
</div>
}
</HoverProject>
))}
</div>
);
}
}
export default ProjectList;
I have no idea what to do with the other component, can you even render childs inside a parent like this ?
I would like pass the props from FollowMouse as style attributes of my img element.
I'm not 100% sure I follow your question, and I don't know what you're expecting this.props.children to do when invoked as a function, but if you want to add props to a child element you can do so via React.cloneElement:
const ParentComponent ({children}) => (
<div>
{React.cloneElement(
React.Children.only(children), // clone the only child...
{ style: { left: xPos, top: yPos} } // ...and add new props
)}
</div>
)
Given:
<ParentComponent>
<div>Wookies and Hats</div>
</ParentComponent>
The child component will get the additional props, the equivalent of:
<ParentComponent>
<div style={{left: xPos, top: yPos}}>Wookies and Hats</div>
</ParentComponent>

Javascript function is working on main page but stops on second pages

I am trying to make a website with ReactJS. In my website I have a slice button on the main page;
When you click the slice button, each side shows a different image.
I add links to the Menu bar as well for the slice button (I am going to call Left - Right) so when you click the link, the first go to the part of the main page then it moves the slice button and shows the image.
I do not have any problem with the links when I am on the main page but the problem is; I am using same Menu bar for all pages and when I click the Left and the Right right links in the Menu on a different page it stops.
The links open the main page but stay the beginning of the main page.
UPDATE 2: Although the problem is not with the Toggle button but I want to add the toggle function in the main page that you can see all.
Note: I remove toggle button but links are not working (Right I cannot see the buttons when I remove the function but when I click the link in the menu, the page does not link to exact part of the button placed in the main page so still I have the same problem .)
Here is my ChangeViewButtonClick function in main;
import React, { Component } from "react";
class Main extends React.Component{
constructor(props) {
super(props);
this.state = {
view: "Left"
};
this.ChangeViewButtonClick = this.ChangeViewButtonClick.bind(this);
this.ChangeViewToggle = this.ChangeViewToggle.bind(this);
}
togglePopup() {
this.setState({
showPopup: !this.state.showPopup
});
}
ChangeViewToggle() {
if (this.refs.toggle.state.checked === true) {
this.setState({ view: "Left" });
this.refs.toggle.state.checked = false
} else {
this.setState({ view: "Right" });
this.refs.toggle.state.checked = true
}
}
ChangeViewButtonClick(view) {
if (view === "Left") {
this.setState({ view: "Left" });
this.refs.toggle.state.checked = true
} else if (view === "Right") {
this.setState({ view: "Right" });
this.refs.toggle.state.checked = false
}
}
render(){
return(
<div>
<h1>
<footer className="togglefooter">
<label>
<span style={{ color: "black", fontWeight: "normal" }}>
Left
</span>
<Toggle
id="toggle"
ref="toggle"
defaultChecked={this.state.view}
icons={false}
onChange={() => this.ChangeViewToggle()}
/>
<span style={{ color: "black", fontWeight: "normal" }}>
Right
</span>
</label>
</footer>
</h1>
</div>
);
}
}
and this is my Menu Page ;
import React from 'react';
import { Collapse, Navbar, NavbarToggler, NavbarBrand, NavLink} from 'reactstrap';
import { HashLink as Link } from 'react-router-hash-link';
class Menu extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false
};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
return(
<Navbar light expand="md">
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<nav className="nav">
<ul className="nav__menu">
<li className="nav__menu-item">
<NavLink to="#" tag= {Link} >
Images
</NavLink>
<Image_SubMenu ChangeViewButtonClick{this.props.ChangeViewButtonClick}/>
</li>
<li className="nav__menu-item">
<NavLink tag={Link} to="/main#something">
Something
</NavLink>
</li>
</ul>
</nav>
</Collapse>
</Navbar>
);
}
}
class Image_SubMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul className="nav__submenu">
<li className="nav__submenu-item ">
<NavLink to="/main#map" tag={Link} onClick={evt => this.props.ChangeViewButtonClick("Left")}>
Left Image
</NavLink>
</li>
<li className="nav__submenu-item ">
<NavLink to="/main#map" tag={Link} onClick={evt => this.props.ChangeViewButtonClick("Right")} >
Right Image
</NavLink>
</li>
</ul>
)
}
}
export default Menu;
How can I fix the link that if I click on the link anywhere on the website then shows me Left or Right images
I found a few articles about delegated events unfortunately I could not figure out.
Thanks in advance
Note that this is only part of the code and just paste the part that you need so if I miss something please tell me so can be a few typos.
Update: I would like to add my other two pages here because the information that I shared might not be enough.
I am trying to click links that are inside the Menu and I have the problem
About page:
import React from "react";
import ReactGA from 'react-ga'
//Import components
import Footer from "./components/Footer.js";
import Menu from "./components/Menu.js";
function About(props) {
return (
<div>
<div className="bgded overlay">
{/* Menu Component */}
<Menu ChangeViewButtonClick={props.ChangeViewButtonClick}/>
</div>
{/* Start of Project Aims */}
<a id ="aims" />
<div className="wrapper row3">
<main className="hoc container clear" style={{ paddingTop: "30px" }}>
<article className="one_third first" style={{ width: "100%" }}>
<h4 className="font-x2 font-x3">
Something about project
</h4>
<p>Something is here too..</p>
</article>
<div className="clear" />
</main>
</div>
{/* Footer Component */}
<Footer />
</div>
);
}
export default About;
Report Page:
import React, { Component } from "react";
import ReactGA from 'react-ga';
import ReactDependentScript from 'react-dependent-script';
import Footer from "./components/Footer.js";
import Menu from "./components/Menu.js"
//Import PNG Image
//import { domain } from "./config.json";
import GraphHover from "./components/GraphHover.js";
class report extends Component {
constructor(props) {
super(props);
this.state = {
displayGraph: "none",
graphTitle: null,
graphPreview: null,
graphHoverLeft: "0",
previewImages: null,
};
this._onMouseLeave = this._onMouseLeave.bind(this);
this.DisplayHoverGraph = this.DisplayHoverGraph.bind(this);
}
_onMouseLeave(evt) {
this.setState({ displayGraph: "none" });
}
DisplayHoverGraph(input, image, evt) {
//Disable hover effect if on a mobile device
if (!this.props.isMobileDevice) {
this.setState({
graphTitle: input,
previewImage: image,
displayGraph: "inline"
});
if (evt.clientX < window.screen.width / 2) {
this.setState({ graphHoverLeft: "1200px" });
} else {
this.setState({ graphHoverLeft: "0" });
}
}
}
componentDidMount() {
console.log(window.test)
}
render() {
return (
<div>
<GraphHover
position="fixed"
top={this.state.graphHoverTop}
left={this.state.graphHoverLeft}
displayPreview={true}
display={this.state.displayGraph}
graphTitle={this.state.graphTitle}
previewImage={this.state.previewImage}
graphHeadingSize={this.state.graphHeadingSize}
width={this.state.graphHoverWidth}
height={this.state.graphHoverHeight}
/>
<div className="bgded overlay">
{/* Menu */}
<Menu ChangeViewButtonClick={this.props.ChangeViewButtonClick} />
</div>
<div className="wrapper row3">
<a id="sumary" />
<main className="hoc container clear" style={{ paddingTop: "30px" }}>
<div className="clear" />
</main>
</div>
<Footer />
</div>
);
}
}
export default report;
You need to pass the prop ChangeViewButtonClick via the parent Class (In your case, Main ) to the child class (In your case the class About or class Report ).
So your structure would be like this -
Main
----About
----Report.
The reason I am mentioning that about and report will be child of your Main class because they are accessing the prop function like this <Menu ChangeViewButtonClick={this.props.ChangeViewButtonClick} /> where this.props.ChangeViewButtonClick should be made available to report and about page.
Note: You might need to add a navigation logic to load the respective pages. I will give a simple snippet below
import React, { Component } from "react";
import Menu from './Menu'
import report from "./report"
import about from "./about"
class Main extends React.Component{
constructor(props) {
super(props);
this.state = {
view: "Left"
};
this.ChangeViewButtonClick = this.ChangeViewButtonClick.bind(this);
}
ChangeViewButtonClick(view) {
if (view === "Left") {
this.setState({ view: "Left" });
this.refs.toggle.state.checked = true
} else if (view === "Right") {
this.setState({ view: "Right" });
this.refs.toggle.state.checked = false
}
}
render(){
return(
<div>
<h1>Main Page is Here </h1>
// Some navigation logic between the pages below
<report onChangeViewButtonClick={this.ChangeViewButtonClick} />
<about onChangeViewButtonClick={this.ChangeViewButtonClick} />
</div>
);
}
}
You have ChangeViewButtonClick function in Main component, but you don't pass this function into Menu component
import React, { Component } from "react";
import Menu from './Menu'
class Main extends React.Component{
constructor(props) {
super(props);
this.state = {
view: "Left"
};
this.ChangeViewButtonClick = this.ChangeViewButtonClick.bind(this);
}
ChangeViewButtonClick(view) {
if (view === "Left") {
this.setState({ view: "Left" });
this.refs.toggle.state.checked = true
} else if (view === "Right") {
this.setState({ view: "Right" });
this.refs.toggle.state.checked = false
}
}
render(){
return(
<div>
<h1>Main Page is Here </h1>
<Menu onChangeViewButtonClick={this.ChangeViewButtonClick} />
</div>
);
}
}
And in Menu component you can receive onChangeViewButtonClick property and call it in action like this.props.onChangeViewButtonClick('view')

Close responsive navigation | React JS

I have a header component which manages the state for my navigation component.
The navigation successfully toggles if the user clicks on the hamburger icon however, if the user clicks or taps anywhere outside of the navigation I need the navigation to close.
How can I achieve this?
Here is my code:
export default class Header extends React.Component {
constructor() {
super();
this.state = {
mobileOpenNav: false
};
bindAll([
'openMobileNav',
'openContactModal'
],this);
}
openMobileNav() {
this.props.contactModalToggle(false);
this.setState({
mobileOpenNav: !this.state.mobileOpenNav
})
}
openContactModal() {
this.props.contactModalToggle();
this.setState({
mobileOpenNav: !this.state.mobileOpenNav
});
}
render() {
const {nav, contactModalToggle, location, logos} = this.props;
const {mobileOpenNav} = this.state;
return (
<div className="header-wrap">
<div className="header">
<Logo location={location} logoUrls={logos} />
<Navigation
location={location}
nav={nav}
contactModalToggle={this.openContactModal}
mobileOpen={mobileOpenNav}
mobileToggle={this.openMobileNav}
/>
<div className="hamburger" onClick={this.openMobileNav}><img src={HamburgerIcon} /></div>
</div>
</div>
)
}
}
The following solution should work for you.
componentDidMount() {
document.addEventListener('click', this.handleClickOutside.bind(this), true);
}
componentWillUnmount() {
document.removeEventListner('click', this.handleClickOutside.bind(this), true);
}
handleClickOutside(e) {
const domNode = ReactDOM.findDOMNode(this);
if(!domNode || !domNode.contains(event.target)) {
this.setState({
mobileOpenNav: false
});
}
}
use react-onclickoutside module https://github.com/Pomax/react-onclickoutside
import onClickOutside from "react-onclickoutside"
import Navigation from "pathToNvaigation"
const ContentWrapper = onClickOutside(Navigation)
and use
<ContentWrapper
location={location}
nav={nav}
contactModalToggle={this.openContactModal}
mobileOpen={mobileOpenNav}
mobileToggle={this.openMobileNav}
/>

Categories