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.
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;
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;
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());
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