How to add class in element on scroll React js - reactjs

How to add class in element on scroll React js, I want to add class in on scroll and remove that class if on top of the page.
import React from "react"
import { Link } from "react-router"
import { prefixLink } from "gatsby-helpers"
import Helmet from "react-helmet"
import { config } from "config"
module.exports = React.createClass({
propTypes() {
return {
children: React.PropTypes.any,
}
},
render() {
window.addEventListener('scroll', (event) => {
});
return (
<div>
<header className="header">
<div className="top-bar">
<span>678-827-2782 </span>
<span> hellohello#knotel.com</span>
<button>Login</button>
</div>
</header>
{this.props.children}
</div>
)
},
})

If you want to use React Hooks in 2020 stateless component
const [scroll, setScroll] = useState(false);
useEffect(() => {
window.addEventListener("scroll", () => {
setScroll(window.scrollY > 50);
});
}, []);
and use it anywhere in your code
className={scroll ? "bg-black" : "bg-white"}
setScroll(window.scrollY > 50); here 50 specify the height.

Use state to manage classnames and update state in the scroll event. Also, you should move the scroll event binding into the componentDidMount function instead of render.
import React from "react"
import { Link } from "react-router"
import { prefixLink } from "gatsby-helpers"
import Helmet from "react-helmet"
import { config } from "config"
module.exports = React.createClass({
propTypes() {
return {
children: React.PropTypes.any,
}
},
componentDidMount(){
window.addEventListener('scroll', () => {
let activeClass = 'normal';
if(window.scrollY === 0){
activeClass = 'top';
}
this.setState({ activeClass });
});
}
render() {
return (
<div>
<header className="header">
<div className={`top-bar ${this.state.activeClass}`}>
<span>678-827-2782 </span>
<span> hellohello#knotel.com</span>
<button>Login</button>
</div>
</header>
{this.props.children}
</div>
)
},
})

import React, { useState, useEffect } from "react"
import { Link } from "react-router"
import { prefixLink } from "gatsby-helpers"
import Helmet from "react-helmet"
import { config } from "config"
export default function Header(props) {
const [scroll, setScroll] = useState(false)
useEffect(() => {
window.addEventListener("scroll", () => {
setScroll(window.scrollY > 10)
})
}, [])
return (
<header className={scroll ? "main-header scrolled" : "main-header"}>
</header>
)
}

One more variant with hooks and cleanup to avoid memory leaks, if the browser is not handled it properly.
const [isScrollValueMoreThanHeaderHeight, setIsScrollValueMoreThanHeaderHeight] = useState(false);
//here 96(px) - height of current header
useEffect(() => {
const handleScroll = () => {
setIsScrollValueMoreThanHeaderHeight(window.scrollY > 96);
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll);
},[])
And then used it in a ternary way to change class
className={isScrollValueMoreThanHeaderHeight ? "class1" : "class2"}

Related

How to force "The onFormSubmit function in hbspt.forms.create requires jQuery. It was not run." Error in Gatsby SPA

I'm trying to implement HubspotForm into my SAP. It render's but when I click submit button I get error "The onFormSubmit function in hbspt.forms.create requires jQuery. It was not run."
How can I force this?
My component:
import React from "react"
import style from "./bottomForm.module.sass"
import BackgroundImage from "gatsby-background-image"
import classNames from "../../helpers/classNames"
import HubspotForm from "react-hubspot-form"
import { graphql, useStaticQuery } from "gatsby"
const BottomForm = ({ image, title, children }) => {
const defaultImage = useStaticQuery(graphql`
query {
form_bg: file(relativePath: { eq: "common/form_bg.jpg" }) {
childImageSharp {
fluid(maxWidth: 1920) {
...GatsbyImageSharpFluid_noBase64
}
}
}
}
`)
return (
<BackgroundImage
Tag="section"
className={style.section}
fluid={image || defaultImage.form_bg.childImageSharp.fluid}
id={"bottomForm"}
>
<div className={style.content}>
<h2 className={style.title}>{title}</h2>
<div className={style.form_box}>
<div className={classNames(style.form_column, style.form_info)}>
{children}
</div>
<div className={style.form_column}>
{/*<div id="contactFormBottom" />*/}
<div className={style.form_contact_box}>
<HubspotForm
portalId="9075801"
formId="6ee5300e-5ffe-471d-a400-92b06ca18a11"
onSubmit={() => console.log('Submit!')}
onReady={(form) => console.log('Form ready!')}
loading={<div>Loading...</div>}
/>
</div>
</div>
</div>
</div>
</BackgroundImage>
)
}
export default BottomForm
For me works next solution ---> I add on "onReady" property code which check if jQuery script is in DOM and add it if it isn't. Also I add "onSubmit={() => {}}". Without it error drop down again.
<HubspotForm
portalId="9075800"
formId="11ba4132-6072-4c18-9ea7-f21b70191120"
loading={<div>Loading...</div>}
onReady={() => {
const script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js';
let jqueryScript = document.getElementsByTagName('script');
let src = Array.from(jqueryScript).filter(item => item.src === script.src)
if(src.length === 0) {
document.body.appendChild(script)
}
}}
onSubmit={() => {}}
/>
Check this GitHub thread's solution:
import React, { useEffect } from 'react';
....
useEffect(() => {
window.addEventListener('message', handler);
return () => {
window.removeEventListener('message', handler);
}
}, [])
function handler(event) {
if (event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmitted') {
if (event.data.id === 'formidhere')
//do stuff
else if (event.data.id === 'formidhere')
//do stuff
}
}
This is one of the solutions proposed. Another one that I will try to avoid is importing jQuery directly on your project.

reactjs: Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>

The error happens in this part:
const {playing, bannerOpen} = useSelector(state => state.musicReducer);
I have searched for information, but I cannot fix it.
Apparently I only get that error in that part, I am a newbie and there are things that I do
not understand yet.
import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "../components/Theme";
import AddMusic from "../fragment/AddMusic";
import BottomNavigationMobile from "../fragment/BottomNavigationMobile";
import CurrentPlayingLarge from "../fragment/CurrentPlayingLarge";
import FooterMusicPlayer from "../fragment/FooterMusic";
import FooterSelectMusic from "../fragment/FooterSelectMusic";
import MobileTopNavigation from "../fragment/MobileTopNavigation";
import MusicCardContainer from "../fragment/MusicCardContainer";
import Navigation from "../fragment/Navigation";
import Playlist from "../fragment/Playlist";
import SideBar from "../fragment/SideBar";
import About from "./About";
import './css/Home.scss';
import Profile from "./Profile";
import Search from "./Search";
import {useSelector} from 'react-redux';
function getCurrPage(pathName) {
switch (pathName) {
case "/home":
return <MusicCardContainer/>
case "/home/search":
return <Search/>
case "/home/profile":
return <Profile/>
case "/home/add":
return <AddMusic/>
case "/home/about":
return <About/>
default:
if (pathName.startsWith("/home/playlist/")){
return <Playlist/>
}
return null
}
}
function Home() {
const [screenSize, setScreenSize] = useState(undefined);
const [currMusic, setCurrMusic] = useState(null);
const [Page, setCurrPage] = useState(<MusicCardContainer/>);
let pathname = window.location.pathname;
useEffect(() => {
setCurrPage(getCurrPage(pathname))
}, [pathname]);
window.addEventListener("resize", handleResize);
function handleResize() {
setScreenSize(window.innerWidth);
}
useEffect(() => {
handleResize();
return () => window.removeEventListener("resize", handleResize);
});
const useStyle = useContext(ThemeContext);
const {playing, bannerOpen} = useSelector(state => state.musicReducer);
useEffect(() => {
setCurrMusic(playing)
}, [playing])
return (
<div style={useStyle.component} className={"home-container"}>
{
screenSize <= 970 ?
<MobileTopNavigation/> :
<Navigation/>
}
<section className={"home-music-container"}>
<div className="sidebar-home">
<SideBar/>
</div>
<div className="main-home">
{
Page
}
</div>
</section>
{
bannerOpen
&&
<section className="current-large-banner">
<CurrentPlayingLarge/>
</section>
}
<React.Fragment>
{
currMusic
?
<FooterMusicPlayer music={currMusic}/>
:
<FooterSelectMusic/>
}
{
screenSize <= 970 && <BottomNavigationMobile/>
}
</React.Fragment>
</div>
);
}
export default Home;

Using typed.js with React function components

typed.js doesn't offer an example for this in the docs, only for class components:
class TypedReactDemo extends React.Component {
componentDidMount() {
const options = { ... };
this.typed = new Typed(this.el, options );
}
render() {
return (
<span ref={(el) => { this.el = el; }} />
);
}
}
import React, { useRef, useEffect } from "react";
import Typed from "typed.js";
const Example = () => {
const typeTarget = useRef(null);
useEffect(() => {
const typed = new Typed(typeTarget.current, {
strings: ["<i>First</i> sentence.", "& a second sentence."],
typeSpeed: 40,
});
return () => {
typed.destroy();
};
}, []);
return <span ref={typeTarget} />;
};
export default Example;

How to test button prop in enzyme

Trying to test this component, and im getting this
error
TypeError: this.props.onItemAdded is not a function
I've referenced this but this solution doesn't really apply to my problem
Enzyme test: TypeError: expect(...).find is not a function
How would i test the button functionality being that the button is a prop ?
todo-add-item.test.js
import React from "react";
import { shallow } from "enzyme";
import TodoAddItem from './todo-add-item';
describe('Should render add item component', ()=> {
it('should render add item component', () => {
const wrapper = shallow(<TodoAddItem/>)
})
})
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
const wrapper =shallow(<TodoAddItem/>)
wrapper.find('button').simulate('click') // getting the type error here.
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
You don't pass any props to your component.
const wrapper =shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
You can check props with .props()
Eg:
console.log('props',wrapper.find('button').props());

Loading Screen on Next.js page transition

I am trying to implement a loading screen when changing routes in my Next.js app, for example /home -> /about.
My current implementation is as follows. I am setting the initial loaded state to false and then changing it on componentDidMount. I am also calling the Router.events.on function inside componentDidMount to change the loading state when the route change starts.
_app.js in pages folder
class MyApp extends App {
constructor(props) {
super(props);
this.state = {
loaded: false,
};
}
componentDidMount() {
this.setState({ loaded: true });
Router.events.on('routeChangeStart', () => this.setState({ loaded: false }));
Router.events.on('routeChangeComplete', () => this.setState({ loaded: true }));
}
render() {
const { Component, pageProps } = this.props;
const { loaded } = this.state;
const visibleStyle = {
display: '',
transition: 'display 3s',
};
const inVisibleStyle = {
display: 'none',
transition: 'display 3s',
};
return (
<Container>
<>
<span style={loaded ? inVisibleStyle : visibleStyle}>
<Loader />
</span>
<span style={loaded ? visibleStyle : inVisibleStyle}>
<Component {...pageProps} />
</span>
</>
</Container>
);
}
}
This works perfectly fine but I feel like there may be a better solution more elegant solution. Is this the only way which isn't cumbersome to implement this loading feature or is there an alternative ?
Using the new hook api,
this is how I would do it..
function Loading() {
const router = useRouter();
const [loading, setLoading] = useState(false);
useEffect(() => {
const handleStart = (url) => (url !== router.asPath) && setLoading(true);
const handleComplete = (url) => (url === router.asPath) && setLoading(false);
router.events.on('routeChangeStart', handleStart)
router.events.on('routeChangeComplete', handleComplete)
router.events.on('routeChangeError', handleComplete)
return () => {
router.events.off('routeChangeStart', handleStart)
router.events.off('routeChangeComplete', handleComplete)
router.events.off('routeChangeError', handleComplete)
}
})
return loading && (<div>Loading....{/*I have an animation here*/}</div>);
}
Now <Loading/> is going to show up whenever the route will change...
I animate this using react-spring, but you can use any library you prefer to do this.
You can even take a step further and modify when the component shows up by modifying the handleStart and handleComplete methods that gets a url.
Why not use nprogress as follows in _app.js
import React from 'react';
import Router from 'next/router';
import App, { Container } from 'next/app';
import NProgress from 'nprogress';
NProgress.configure({ showSpinner: publicRuntimeConfig.NProgressShowSpinner });
Router.onRouteChangeStart = () => {
// console.log('onRouteChangeStart triggered');
NProgress.start();
};
Router.onRouteChangeComplete = () => {
// console.log('onRouteChangeComplete triggered');
NProgress.done();
};
Router.onRouteChangeError = () => {
// console.log('onRouteChangeError triggered');
NProgress.done();
};
export default class MyApp extends App { ... }
Link to nprogress.
You also need to include style file as well. If you put the css file in static directory, then you can access the style as follows:
<link rel="stylesheet" type="text/css" href="/static/css/nprogress.css" />
Make sure the CSS is available in all pages...
It will work for all your routes changing.
For anyone coming across this in 2021, the package nextjs-progressbar makes this super easy. In your Next.js _app.js, simply add:
import NextNProgress from 'nextjs-progressbar';
export default function MyApp({ Component, pageProps }) {
return (
<>
<NextNProgress />
<Component {...pageProps} />;
</>
);
}
And done!
Demo and screenshot:
New Update with NProgress:
import Router from 'next/router'
import Link from 'next/link'
import Head from 'next/head'
import NProgress from 'nprogress'
Router.events.on('routeChangeStart', (url) => {
console.log(`Loading: ${url}`)
NProgress.start()
})
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
export default function App({ Component, pageProps }) {
return (
<>
<Head>
{/* Import CSS for nprogress */}
<link rel="stylesheet" type="text/css" href="/nprogress.css" />
</Head>
<Component {...pageProps} />
</>
)
}
If you use Tailwind CSS, copy the code from here: https://unpkg.com/nprogress#0.2.0/nprogress.css and paste the code into your global CSS file.
if you want to disable the spinner add the below code in your _app.tsx/jsx file and remove the spinner styles from CSS.
NProgress.configure({ showSpinner: false });
Source Links:
https://github.com/rstacruz/nprogress
https://nextjs.org/docs/api-reference/next/router
Progress bar like NProgress in 90 lines of code (vs NProgress v0.2.0 is 470 lines .js + 70 lines .css):
import { useEffect, useReducer, useRef } from 'react';
import { assert } from './assert';
import { wait } from './wait';
import { getRandomInt } from './getRandomNumber';
let waitController: AbortController | undefined;
// https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047
export function useProgressBar({
trickleMaxWidth = 94,
trickleIncrementMin = 1,
trickleIncrementMax = 5,
dropMinSpeed = 50,
dropMaxSpeed = 150,
transitionSpeed = 600
} = {}) {
// https://stackoverflow.com/a/66436476
const [, forceUpdate] = useReducer(x => x + 1, 0);
// https://github.com/facebook/react/issues/14010#issuecomment-433788147
const widthRef = useRef(0);
function setWidth(value: number) {
widthRef.current = value;
forceUpdate();
}
async function trickle() {
if (widthRef.current < trickleMaxWidth) {
const inc =
widthRef.current +
getRandomInt(trickleIncrementMin, trickleIncrementMax); // ~3
setWidth(inc);
try {
await wait(getRandomInt(dropMinSpeed, dropMaxSpeed) /* ~100 ms */, {
signal: waitController!.signal
});
await trickle();
} catch {
// Current loop aborted: a new route has been started
}
}
}
async function start() {
// Abort current loops if any: a new route has been started
waitController?.abort();
waitController = new AbortController();
// Force the show the JSX
setWidth(1);
await wait(0);
await trickle();
}
async function complete() {
assert(
waitController !== undefined,
'Make sure start() is called before calling complete()'
);
setWidth(100);
try {
await wait(transitionSpeed, { signal: waitController.signal });
setWidth(0);
} catch {
// Current loop aborted: a new route has been started
}
}
function reset() {
// Abort current loops if any
waitController?.abort();
setWidth(0);
}
useEffect(() => {
return () => {
// Abort current loops if any
waitController?.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return {
start,
complete,
reset,
width: widthRef.current
};
}
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useProgressBar } from './useProgressBar';
const transitionSpeed = 600;
// https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047
export function RouterProgressBar(
props?: Parameters<typeof useProgressBar>[0]
) {
const { events } = useRouter();
const { width, start, complete, reset } = useProgressBar({
transitionSpeed,
...props
});
useEffect(() => {
events.on('routeChangeStart', start);
events.on('routeChangeComplete', complete);
events.on('routeChangeError', reset); // Typical case: "Route Cancelled"
return () => {
events.off('routeChangeStart', start);
events.off('routeChangeComplete', complete);
events.off('routeChangeError', reset);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return width > 0 ? (
// Use Bootstrap, Material UI, Tailwind CSS... to style the progress bar
<div
className="progress fixed-top bg-transparent rounded-0"
style={{
height: 3, // GitHub turbo-progress-bar height is 3px
zIndex: 1091 // $zindex-toast + 1 => always visible
}}
>
<div
className="progress-bar"
style={{
width: `${width}%`,
//transition: 'none',
transition: `width ${width > 1 ? transitionSpeed : 0}ms ease`
}}
/>
</div>
) : null;
}
How to use:
// pages/_app.tsx
import { AppProps } from 'next/app';
import Head from 'next/head';
import { RouterProgressBar } from './RouterProgressBar';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>My title</title>
<meta name="description" content="My description" />
</Head>
<RouterProgressBar />
<Component {...pageProps} />
</>
);
}
More here: https://gist.github.com/tkrotoff/db8a8106cc93ae797ea968d78ea28047

Resources