Only load Snipcart on specific page in Gatsby - reactjs

I'm using Snipcart Plugin in Gatsby but the script gets loaded everywhere. Is is it possible with some sort of function to trigger the script on only 1 specific page and not entirely?
Below are the options I'm using in my Gatsby-config.js file
{
resolve: "gatsby-plugin-snipcart",
options: {
apiKey: process.env.SNIPCART_API,
autopop: true,
js: "https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.js",
styles: "https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.css",
jquery: false,
},
},

You should take a look at gatsby-plugin-snipcartv3. I believe the gatsby-plugin-snipcart is deprecated and is not working with Snipcart v3.
But as far as I know, there's no way to tell the plugin to load the script only on specific pages.

You could use Snipcart directly, not using the plugin, to have more control over it.
Let's say you have a layout.js file, wrapping content for your page, you can have a loadSnipcart flag that will load Snipcart files only when you need them.
Here's an example:
layout.js
import React from "react"
import Helmet from "react-helmet"
export default ({ loadSnipcart, children }) => {
const Snipcart = () => {
if (!loadSnipcart) return null
return (
<Helmet>
<script
src="https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.js"
type="text/javascript"
/>
<link
href="https://cdn.snipcart.com/themes/v3.0.8/default/snipcart.css"
rel="stylesheet"
/>
</Helmet>
)
}
return (
<div id="main-content">
<Snipcart />
{children}
</div>
)
}
shop.js
import React from "react"
import Layout from "./layout"
export default () => {
return (
<Layout loadSnipcart>
<h1>Welcome to my shop !</h1>
</Layout>
)
}
index.js
import React from "react"
import Layout from "./layout"
export default () => {
return (
<Layout>
<h1>This page doesn't load Snipcart</h1>
</Layout>
)
}

Related

How to both dynamically import and render a component on button click in nextjs?

I know that there are several nextjs and reactjs questions out there in this topic but I think this is specific enough to get an own thread. So I have a NextJS project where I want a button to disappear on click and then import a component dynamically and render it.
It is crucial to import the component dynamically because of 2 things.
because we work with a library in the component which relies on 'window'
because the component affects load time and we only need it when the 'init button' is clicked
Here is my index.js code so far:
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import dynamic from "next/dynamic"
import React, { useEffect, useState } from "react";
import { Inter } from "#next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
return (
<>
<Head>
<title>My page</title>
<meta
name='description'
content="Some desc. content"
/>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<button onClick={initSurvey()}>Initialize!</button>
</>
)
}
function initSurvey(){
console.log("The button is working!")
const Survey = dynamic(() => import("../components/SurveyComponent"), {
ssr: false,
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Please keep in mind this is not all I could do I just cleaned up the failed attempts to deliver you a clean, readable and understandable snippet.
Thank you in advance!
By checking the React.lazy documentation and this guide, I managed to get to this solution:
import { lazy, Suspense, useState } from "react";
const dynamicComponent = lazy(() => import('../components/MyDynamicComponent');
export default function Home() {
const [dynComp, setDynComp] = useState(<div/>);
const loadDynamicComponent = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<dynamicComponent/>
</Suspense>
)
}
return (
<>
<button onClick={ () => setDynComp(<div>{loadDynamicComponent()}</div>)}>Initialize!</button>
{ dynState }
</>
);
}
Test it and let me know if it helps you!

How to use mantine UI with next 13

I was trying to use mantine UI with NextJS 13 using the app directory,
I have to wrap everything in the MantineProvider component but I don't know where to put it.
I tried this
layout.js
/* eslint-disable #next/next/no-head-element */
import { MantineProvider } from '#mantine/core';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
/** Put your mantine theme override here */
colorScheme: 'dark',
}}>
<html>
<head></head>
<body>{children}</body>
</html>
</MantineProvider>
);
}
and it didn't work
so, is there any solution??
So I've been interested in solving this problem too.
Step 1 is moving third-party providers to a "client-only" component.
See here
The next step is to follow this thread on mantine's github, while they work out compatibility issues with emotion & next13
Lastly, this seems to be the only official implementation example on Mantine's github using Mantine with the new Next.js app directory.
Here's how they approached it:
/app/emotion.tsx
"use client";
import { CacheProvider } from "#emotion/react";
import { useEmotionCache, MantineProvider } from "#mantine/core";
import { useServerInsertedHTML } from "next/navigation";
export default function RootStyleRegistry({
children
}: {
children: React.ReactNode
}) {
const cache = useEmotionCache();
cache.compat = true;
useServerInsertedHTML(() => (
<style
data-emotion={
`${cache.key} ${Object.keys(cache.inserted).join(" ")}`
}
dangerouslySetInnerHTML={{
__html: Object.values(cache.inserted).join(" "),
}}
/>
));
return (
<CacheProvider value={cache}>
<MantineProvider withGlobalStyles withNormalizeCSS>
{children}
</MantineProvider>
</CacheProvider>
)
}
/app/layout.tsx
import RootStyleRegistry from './emotion';
export default function RootLayout({ children }) {
return (
<html lang="en-US">
<head />
<body>
<RootStyleRegistry>{children}</RootStyleRegistry>
</body>
</html>
);
}
Hope this helps. Let me know if you get it working

Pass page variables to layout in NextJS

I'm migrating my blog from Jekyll to NextJS and want to achieve the same functionality with the site layout.
The idea is to have metatags defined in a single layout file and fill values with variables that are defined on a page level.
I saw several solutions, one of them is to define metatags in _app.js as described here:
NextJS pass class from page to layout component
but from my understanding so far as a newbie in React/NextJS, it's better to utilize pageProps, but I can't figure out how to code it.
So far I have
_app.js
import 'bootstrap/dist/css/bootstrap.css'
import Layout from '../components/layout/layout';
export default function Blog({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
layout.js
import { Fragment } from 'react';
import Image from 'next/image';
import Head from 'next/head';
import MainNavigation from './main-navigation';
export default function Layout({ children }) {
return (
<Fragment>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>{children.title}</title>
</Head>
<MainNavigation />
<main>
{children}
</main>
</Fragment>
);
}
HomePage.js
export default function HomePage() {
return <div>HomePage</div>
}
I wanted to stick to the original code examples from the official documentation, so left layout as described in question, just
<Layout>
not
<Layout metas={pageProps.metas} ...>
So I just had to define props using getStaticProps:
export async function getStaticProps() {
return { props: { title: 'HomePage' } }
}
export default function HomePage() {
return <div>HomePage</div>
}
and then call it in layout as
{children.props.title}
not
{children.title}
The latter one would work if I just define a regular js variable in HomePAge.js as described in another SO thread I referenced. But I'm not sure if this approach somehow affects static website generation, so I decided to use NextJS built-in feature getStaticProps.
You can take the props you want in pageProps and pass them to the layout component:
<Layout metas={pageProps.metas} ...>
And use them in the <Layout />:
export default function Layout({ metas, children }) {
return (
<Fragment>
<Head>
<title>{metas.title}</title>
</Head>
...
</Fragment>
);
}

Pages are reloaded instead of routed in shopify next js app

I followed Shopify's guide, until the end of 4th step, to develop a Next JS app and I've setup two pages (embedded app navigation), Home and Page1.
Now, when I click to open both pages, the app is doing a reload instead of routing...
You can see here the flickering issue - https://youtu.be/45RvYgxC7C0
Any help on this would be very appreciated.
_app.js
import React from "react";
import App from "next/app";
import Head from "next/head";
import { AppProvider } from "#shopify/polaris";
import { Provider } from "#shopify/app-bridge-react";
import Cookies from "js-cookie";
import "#shopify/polaris/dist/styles.css";
import "../css/styles.css";
import lang from "#shopify/polaris/locales/en.json";
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
const config = { apiKey: API_KEY, shopOrigin: Cookies.get("shopOrigin"), forceRedirect: true };
return (
<React.Fragment>
<Head>
<title>My App</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="favicon.ico" />
</Head>
<Provider config={config}>
<AppProvider i18n={lang}>
<Component {...pageProps} />
</AppProvider>
</Provider>
</React.Fragment>
);
}
}
home.js
import React from "react";
import { Page, Layout, Card, FooterHelp, Link } from "#shopify/polaris";
export default function Home() {
return (
<Page title="Home">
<Layout>
<Layout.Section>
<Card title="Online store dashboard" sectioned>
<p>View a summary of your online store’s performance.</p>
</Card>
</Layout.Section>
<Layout.Section>
<FooterHelp>
Learn more about{" "}
<Link url="#" external>
our app
</Link>
</FooterHelp>
</Layout.Section>
</Layout>
</Page>
);
}
Page1.js
import React from "react";
import { Page, Layout, Card, FooterHelp, Link } from "#shopify/polaris";
export default function Page1() {
return (
<Page title="Page1">
<Layout>
<Layout.Section>
<Card title="Online store dashboard" sectioned>
<p>View a summary of your online store’s performance.</p>
</Card>
</Layout.Section>
<Layout.Section>
<FooterHelp>
Learn more about{" "}
<Link url="#" external>
our app
</Link>
</FooterHelp>
</Layout.Section>
</Layout>
</Page>
);
}
When using Shopify's app-bridge, it has a default behavior of navigating to a new route within the iframe that holds your app (and thus completely reloading the app), whereas React implements a client-side router.
Shopify doesn't provide a 100% plug-and-play solution for using client-side routing, but they do make it pretty easy with their ClientRouter component.
The examples on that page are for react-router, not Next.js's router, but the same idea applies to next/router.
For example, a simple router component could look like:
import {useEffect, useContext} from 'react';
import Router, { useRouter } from "next/router";
import { Context as AppBridgeContext } from "#shopify/app-bridge-react";
import { Redirect } from "#shopify/app-bridge/actions";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
const RoutePropagator = () => {
const router = useRouter();
const { route } = router;
const appBridge = React.useContext(AppBridgeContext);
// Subscribe to appBridge changes - captures appBridge urls
// and sends them to Next.js router. Use useEffect hook to
// load once when component mounted
useEffect(() => {
appBridge.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return appBridge && route ? (
<ShopifyRoutePropagator location={route} app={appBridge} />
) : null;
}
export default RoutePropagator;
After creating that component, drop it in the _app.js file inside the Shopify routers, for example:
<Provider config={config}>
<AppProvider i18n={translations}>
<RoutePropagator />
<ApolloProvider client={client}>
// child components
</ApolloProvider>
</AppProvider>
</Provider>
When _app loads, it will now subscribe to changes from appBridge and let appBridge know to send a signal to the client rather than reload the entire iframe. If you apply any routing within the app, such as one page to another, it will also now update the browser's address bar.
Everything works correctly, you are loading the whole page every time you request a new nextjs page. In order to have parts of your layout persistent between page loads, you need to move them to the _app.js.
Take a look at the official dynamic app layout example.
If you want to load a sub-section of the page without reloading the whole page you can use a query in combination with shallow routing e.g example.com/settings and example.com/settings?section='profile'

Initialize script in componentDidMount – runs every route change

I am working on a navbar for my react app (using gatsbyjs to be precise). In the navbar I have a marquee that I initialize in the navbar component in componentDidMount.
It works as intended, but upon every route change componentDidMount will run again which results in the marquee speeding up for every page change, making it go faster and faster.
Is this expected behaviour? And if so, how do I make sure that the script is only run once?
navbar.js
import React from 'react';
import { Link } from 'gatsby';
import styles from '../styles/navbar.module.css';
import NewsMarquee from './newsMarquee';
import Marquee3k from 'marquee3000';
const topLevelNav = [
{
href: '/news',
label: <NewsMarquee/>,
extraClass: styles.navLinkNews,
mediaQueryClass: styles.navLinkHiddenSmall,
},
];
export default class Navbar extends React.Component {
componentDidMount() {
Marquee3k.init();
}
render() {
return (
<div>
<header className={styles.navbar} role="banner">
<nav className={styles.nav}>
{topLevelNav.map(({ href, label, extraClass = '', mediaQueryClass = '' }) => (
<Link
key={label}
to={href}
className={`${styles.navLink} ${extraClass} ${mediaQueryClass} ${menuItemsHidden}`}
activeClassName={styles.navLinkActive}
>
{label}
</Link>
))}
</nav>
</header>
</div>
)
}
}
newsMarquee.js
import React from 'react';
import { StaticQuery, graphql } from "gatsby";
import styles from '../styles/newsMarquee.module.css';
export default () => (
<StaticQuery
query={graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC } limit: 10) {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "YYYY.MM.DD")
}
fields {
slug
}
}
}
}
}
`}
render={data => (
<div className={`marquee3k ${styles.marquee}`}>
<div>
{data.allMarkdownRemark.edges.map(({ node }) => (
<span className={styles.marqueeItem} key={node.id}>
{node.frontmatter.date} {node.frontmatter.title}
</span>
))}
</div>
</div>
)}
/>
)
Since I'm using GatsbyJS I went with this plugin from V1, which makes my layout component persist across pages.
gatsby-plugin-layout
This plugin enables adding components which live above the page components and persist across page changes.
This can be helpful for:
Persisting layout between page changes for e.g. animating navigation
Storing state when navigating pages
Custom error handling using componentDidCatch
Inject additional data into pages using React Context.
This plugin reimplements the behavior of layout components in gatsby#1, which was removed in version 2.

Resources