How to load script in specific page in gatsby - reactjs

Hello.
I recently got stuck with pretty important thing on my gatsby site.
I have to import script from other site cause it is providing map widget. This is the widget from polish delivery company and it is only available under link https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js.
It is activated by a function window.PPWidgetApp.toggleMap(). Problem is when i try to activate, html and css markup from widget are showing but map coming from js it is not.
Here is how I'm loading the script:
{
resolve: "gatsby-plugin-load-script",
options: {
src:
"https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js",
},
},
When I'm on specific route where I'm using this widget and i refresh the page everything is working properly. So I'm guessing problem is that when this script is loaded in index it gets cached somehow by gatsby and most of the important features are not working. So can I load the script only when I'm on let's say route /delivery ? Or is there another, better way to load this script that may work fine ?
Link to github repo with this problem: https://github.com/Exanderal/gatsby-problem

The easiest, native and built-in way to achieve is using <Helmet> component. Basically, this component embeds everything that is inside in your <head> tag.
The problem using it is that if you need to activate or to wait for its loading to make some actions (like window.PPWidgetApp.toggleMap() in your case), it could be kind of buggy since sometimes it may load properly but sometimes not. I will show you different approaches to check which one fits you better.
<Helmet> approach:
<Helmet>
<script src="https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js"/>
</Helmet>
As I said, this workaround may work for standalone scripts, but if you need to perform actions or wait for its loading it may not work. The next approach should fit you.
Custom script loading approach:
const addExternalScript = (url, callback) => {
const script = document.createElement('script');
script.src = url;
script.async=true;
script.onload = callback;
document.body.appendChild(script);
};
useEffect(()=>{
addExternalScript("https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js",window.PPWidgetApp.toggleMap())
},[])
Basically, you are setting a custom function (addExternalScript) that creates the same script tag as the first approach and embeds the passed URL as a first function parameter. The second parameter is the callback function to trigger once it's loaded in the onload function.
Everything it's triggered in the useEffect function with empty deps ([]). The useEffect is a hook (available in React version ^16) that is triggered once the DOM tree is loaded, in this case, it's a nice way to ensure that the window object is properly loaded and set to avoid some common issues in Gatsby using global objects.

Related

In Next.js 13 app directory, how do I incrementally generate new pages?

I have multiple items from a CMS. /items/1 all the way to /items/9999. The content is immutable, so I don't have to worry about revalidateing them.
However, items do get added to the CMS frequently, maybe multiple times in a day. I want to make a static website. How can I add new static pages incrementally?
The CMS isn't handled by me, so there's no way I can add a hook.
As per the docs, by default, route segment parameters that were not statically generated at build-time by generateStaticParams function will be generated on demand. These non-generated segments will use Streaming Server Rendering. This is basically the equivalent to fallback: true on getStaticPaths function on pages folder page components.
Just make sure to perform the appropriate checks on your page component in case the requested data doesn't exist in the CMS. That way you can throw a Not Found error and render a 404 UI making use of the not-found.js file. Example from the docs:
import { notFound } from 'next/navigation';
export default async function Profile({ params }) {
const user = await fetchUser(params.id);
if (!user) {
notFound();
}
// ...
}

How do I use custom Javascript in Next js

Coming from React, i am really confused. In pages/index.js, suppose I have a button with onClick listener, and clicking on that button will log "you clicked" in the console. How do i implement this? I want that page to be statically generated and also give that button some functionality.
The reason I am having a lot of trouble is because in React tutorials or even in my projects, if i needed some functionality i'd do this:
function handleClick() {
document.body.style.background = "black"
console.log("you clicked") //nothing is logged in console
}
export default function App() {
return(
<button onClick{() => handleClick}>Click Me</button>
)
}
I was gonna use this Next.js to see how state works. But I encountered a different problem. Unless I use inline function in onClick, it doesnt work. If I use a seperate handleClick function, the DOM doens't even show that I had an onclick event. I learned that's because Nextjs is rendered server side, so it doesnt have access to DOM and console etc. Then how do i do this?
I just transitioned from React, and in every tutorial, those guys would use handleClick func or whatever to handle events and stuff. But I couldnt find a solution to do this in Next, how does everyone handle this then? Because pages have interactive buttons right? Are those pages not statically generated then?
You forgot call function handleClick:
<button onClick{() => handleClick()}></button>
the same way you do it in react with your onClick function
Static generation pre-rendering does not change the interactivity of any page, check the following from Next.js documentation :
Each generated HTML is associated with minimal JavaScript code
necessary for that page. When a page is loaded by the browser, its
JavaScript code runs and makes the page fully interactive. (This
process is called hydration.)
https://nextjs.org/docs/basic-features/pages

Render AEM pages in React dynamically independently of URL path

I have the need to be able to render any of the pages in my AEM model.json dynamically regardless of the current URL in a SPA React app.
My AEM model.json structure has pages following the /<country>/<language>/rest/of/path format, but I want to be able to strip the country/language and just use the rest of the URL path.
I am able to do this when I initialize the ManagerModel with a the desired path like this:
const path = `/path/to/<my_model>.model.json`
/* initialize the ModelManager with the path to the model.json */
ModelManager.initialize({ path })
/*
grab the desired section of the model and render by calling ReactDOM.render
By doing this I am able to render the section of the model that maps /us/en/user-account` for
example, and render the correct content even though the current browser path is `/`
*/
ModelManager.getData(`/us/en/<page_to_load>`).then(render)
When I handle navigation with history.push (I use react-router), I want to be able to render another page following the same logic. By default, having executed ModelManager.getData("/us/en/<page_to_load>"), every page that I navigate to then renders that same portion of the model.
To fix this, I have tried many variations of ModelManager.getData() calls with no success. The only thing that I have been able to have any success with is dynamically passing the path to the next page to render to a callback function that is defined on the index.js level and passed down as a prop to App.js. The callback triggers another ReactDOM.render call and loads the page correctly regardless of what the actual URL path is. That code looks something like this:
<App
reRender={(path) => {
/* manipulate the path so that it properly maps to the correct AEM model data */
const updatedPath = `/us/en/${path}`
/*
this works, but causes another ReactDOM.render call every time that the current page is
changed
*/
ModelManager.getData(updatedPath).then(render)
}}
/>
There are also cases where the page that has been navigated to doesn't have a corresponding path in the modelStore mapping. I am able to handle that like this:
const pathToInsert = `/<country>/<language>/${window.location.pathname}.model.json`
ModelManager.modelStore.insertData(pathToInsert)
ModelManager.getData(pathToInsert).then(render)
/*
I have been having issues with this, but can get the newly inserted model to load properly by
re-routing back to the current path from the current path
*/
this.props.history.push(window.location.pathname)
I have read and re-read the documentation here and I am unable to figure out the correct way to do what I want to do. The above solutions work for the most part, but are pretty hacky and I would like to find out the proper way to accomplish this. Any help is greatly appreciated!
Yes, I found a work around solution. The AEM library can't handle this requirement out of the box unfortunately. My solution was to make a wrapper for my App component. In that wrapper I initialize the model manager, clone the data and store it in local stage. Then you can conditionally modify the model and pass it along as a prop to your App component. If you have a good understanding of how AEM model data is mapped you should be able to figure out a way to display what you want. You can also fetch and insert models into your master model's ":children" prop (think that is the field name, have not looked in a while).

Reactivesearch & Reactivemaps search routing

Recently I started using Reactivesearch and Reactivemaps by Appbase.io on my website to search my database of items and render them and so on. Most of the components work as they should and some have minor bugs. So recently I moved my component into its own component and then placed that on my nav bar for global access. The issue I then encountered was "how can I route back to my /explore page with my search value?". Evidently its not as simple as I thought.
I setup my search page with a function toPath that takes the path and the value that comes from onValueSelected
toPath(path, obj) {
this.props.history.push(`${path}?q="${obj.term}"`);
console.log('SearchVal: ',obj)
}
Note that term is added because its the object.term and term is the string from your search.
So I try this and I have URLParams={true} on my <ReactiveGoogleMap/> and <CategorySearch/> set and I select my value, and it takes me to the /explore page but my map is centered on item 0 of the elastic database.
I tried a few other methods, passing just the object, removing URLParams and so on. Nothing seems to work.
If anyone has any idea how this could be achieved I would greatly appreciate it, its just this minor issue but still a problem down the road and the documentation lacks how to do this / any examples for the public.
Here is the current config:
toPath(path, obj) {
this.props.history.push(`${path}?q="${obj.term}"`);
console.log('SearchVal: ',obj)
}
<CategorySearch
className="searchbar"
dataField={["restaurantName"]}
categoryField="city.keyword"
placeholder='Let's Search"'
componentId="q"
URLParams={true}
...
<ReactiveGoogleMap
componentId="map"
dataField="location"
stream={true}
defaultZoom={13}
pagination
autoCenter={false}
onPageChange={() => {
window.scrollTo(0, 0);
}}
style={{
}}
onPopoverClick={this.onPopoverClick}
className="rightCol"
showMarkerClusters={false}
showSearchAsMove={false}
searchAsMove={true}
//this.props.location.state.reactive_s
//The old way I passed the object I used this ^
react={{
and: [
"q"
],
}}
Also as a side note, i'm not sure if anyone else is getting this but I get these constant issues from #emotion and its a problem with Reactivesearch
serialize.browser.cjs.js:149 Functions that are interpolated in css calls will be stringified.
If you want to have a css call based on props, create a function that returns a css call like this
let dynamicStyle = (props) => css`color: ${props.color}`
It can be called directly with props or interpolated in a styled call like this
let SomeComponent = styled('div')`${dynamicStyle}`
This is also an issue with reactivesearch and makes it very difficult to work because you can't exactly hide it without hiding everything else. It happens with every click and scroll of the page that has a reactive component. Only on dev branches, never on compiled builds.

Download SVGs rendered by React

I would like to implement an export function on my SPA, consisting on generating a bunch of SVGs (generated using React JSX) and downloading them one at once.
The number of files being huge, I can't display them at the same time on the browser. I have found React Download SVG which permits to download a SVG which is already inthe DOM.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
How could I download all my SVGs (zipping them in a file would be an advantage) without displaying them ?
Thanks in advance,
I worked with similiar problem - generate parametrized SVG paths for CNC purpose.
Problem wasn't with download as invoked manually (after DOM update) - batch download (zipped) planned, also.
Problem was: how to display SVG source/xml in another node/component for debugging - updated on every parameter change.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
This is true ... is nome sense, even harder while React Fiber can delay some DOM updates - but we have some possibilities to be notified.
componentDidUpdate() - but 'not called for the initial render'
ref callback - but '... defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element'
I prefer 2nd method for a few reasons, f.e. neutral to SVG (functional) component internals - however it's not guaranteed to be called in every use case - it won't be if not needed - OTOH CDM and CDU are guaranteed to have updated/proper refs when called.
Another hint found somewhere (SO?): use setTimeout to be safe/sure callback called after DOM update.
downloadableReference = el => {
console.log("DWNLD_REF ",el);
this.svgElement = el // save in wrapper, prevent old ref usage
if( !!el ) { // not null - second pass, fresh, updated
console.log("DWNLD_REF ready ", el.outerHTML )
setTimeout( ()=>{
this.notifyDownloadableDependants() // safest way - will be called after CDU
}, 0 )
}
}
This can be combined to batch the downloading. For UX I would render them sequentially - with progress visualisation, cancellable processing etc.
Another possibility: - maybe (not tested) use of react-jsx-parser be helpfull?

Resources