Gatsby 3rd party scripts not working as intended - javascript

I have the problem with including fastspring to my gatsby project. Problem is the following: I add the script in html head but it doesn't work on all pages(it works only if I refresh the page)
I tried to fix that by inserting script in html.js, with gatsby-ssr.js and gatsby-browser.js
gatsby-browser.js
I put the same code in gatsby-ssr.js, I have also tried with Helmet but nothing works for me
I want it to work on all the pages without needing to refresh the page, so if somebody could help me with this. Thanks in advance! :)

Seems like an old issue, but someone might still have the problem.
I did search for a solution to it for some time and in the end, I went with the following.
I created a component called FastSpringStoreApi.js. It loads FastSpring API and subscribes to 2 callback events from it - data-popup-closed and data-data-callback. I used this two to dispatch custom js events for which I listen in my FastSpring provider. These 2 events contain all information needed for store to function (items, pricing, cart information)
Note: there is a reason I save data into sessionStorage. The event can be dispatched before React hydrates. And in cases like this I take data in session storage as initial state in my reducers.
import React from 'react';
import {
FS_EVENTS,
FS_SESSION_KEY
} from './FastSpringStore.keys';
export default ({ storeFrontId }) => (
<>
<script
type="text/javascript"
dangerouslySetInnerHTML= {{
__html:`
function raiseFSPopupCloseEvent(data) {
var popupCloseEvent = new CustomEvent(
'${FS_EVENTS.POPUP_CLOSE}',
{ detail: data }
);
window.dispatchEvent(popupCloseEvent);
}
function raiseFSDataUpdateEvent(data) {
var dataUpdateEvent = new CustomEvent(
'${FS_EVENTS.DATA_UPDATE}',
{ detail: data }
);
window
.sessionStorage
.setItem(
'${FS_SESSION_KEY}',
JSON.stringify(data)
)
window.dispatchEvent(dataUpdateEvent);
}
`
}}
/>
<script
id="fsc-api"
src="https://d1f8f9xcsvx3ha.cloudfront.net/sbl/0.8.5/fastspring-builder.min.js"
type="text/javascript"
data-popup-closed="raiseFSPopupCloseEvent"
data-data-callback="raiseFSDataUpdateEvent"
data-continuous="true"
data-storefront={storeFrontId}
/>
</>
)
I load this component inside gatsby-ssr.js only
export const onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
<FastSpringStoreApi
key="fast-spring-store-api"
storeFrontId="..."
/>,
])
}
I have FasSpringStore provider where I subscribe to my fs events. Looks like this. With it I can get all data needed further down to any of the components.
useEffect(() => {
const onPopupClosed = (data) => {
// Popup was closed and order was finished (we have order id)
if (_has(data.detail, 'id')) {
// Navigate to home page
// TODO: Show thank you page in the future
navigate('/')
dispatch(cleanShopStore())
}
}
const onDataUpdate = (data) => {
dispatch(
setOrderInfo(
mapFSDataToPayload(
data.detail
)
)
)
}
window.addEventListener(FS_EVENTS.POPUP_CLOSE, onPopupClosed, false);
window.addEventListener(FS_EVENTS.DATA_UPDATE, onDataUpdate, false);
return () => {
window.removeEventListener(FS_EVENTS.POPUP_CLOSE, onPopupClosed)
window.removeEventListener(FS_EVENTS.DATA_UPDATE, onDataUpdate)
window.sessionStorage.removeItem(FS_SESSION_KEY)
}
}, [])
Inside gatsby-ssr.js I wrap my root element with store provider
export const wrapRootElement = ({ element }) => (
<FastSpringStoreProvider>
{element}
</FastSpringStoreProvider>
);
Same goes for gatsby-browser.js
export const wrapRootElement = ({ element }) => (
<FastSpringStoreProvider>
{element}
</FastSpringStoreProvider>
);
Hope this gives some ideas for FastSpring implementation.

Related

Why does my React app not re-render or display changes on the DOM except I relaod?

import { useEffect, useState } from "react";
function Popular() {
const [popular, setPopular] = useState([]);
useEffect(() => {
getPopular();
}, []);
const getPopular = async () => {
const api = await fetch(
`https://api.spoonacular.com/recipes/random?apiKey=${process.env.REACT_APP_RECIPE_API_KEY}&number=9`
);
const data = await api.json();
setPopular(data.recipes);
};
return (
<div>
{popular.map((recipe) => {
return (
<div>
<p>{recipe.title}</p>
</div>
);
})}
</div>
);
}
export default Popular;
I am pretty new to React, and I encountered this issue which I have been trying to fix to no avail. The code is a component that is to return a list of recipe title to my app. I am fetching data from an API in the getPopular() function which is set to the setPopular function variable of the useState() method. But when I save my work and return to the browser, the changes does not display. The list does not display, but if I console.log(data.recipes) it displays on the console.
Before now, if I made any change (maybe a text change) the React app renders it without reloading, but now I have to reload the page before I see the change.
Please how do I fix this issue? So that I can see changes without having to reload the page manually.
Not saying that this is the problem, but getPopular() should not be called after its declaration? By this I mean:
const getPopular = async () => {
const api = await fetch(
/...
};
useEffect(() => {
getPopular();
}, []);
Another thing that bugs me is, although JS/React is case sensitive, I really think you should avoid having a const called popular, since your functions is Popular.
Please, let me know if the order did matter for your problem. I will review some react classes soon, if i get another inside, i'll let you know.

How to mock loadable.lib to make the library be loaded synchronously in some unit tests?

We now use lazy-loading through loadable.lib for about 20 new files which used to load the npm module react-toastify synchronously. The changes are waiting in a draft PR but it seems that the unit tests are broken because they do not wait for the loadable.lib-passed module to be loaded.
Expected results
Be able to mock loadable.lib so that it works exactly like before but loads the given library synchronously and in the unit test this is seen as the children of loadable.lib resulted Component have access to that library and a first render does this successfully.
Actual results
The old snapshot (full of tags and nested things and props) and the new one (null) are not matching. These do not work:
// TODO: not working because loadable is used in many places
// and children are not always enough to render to avoid crashes,
// and even just with children there can be crashes
jest.mock('#loadable/component', (loadfn) => ({
lib: jest.fn(() => {
return { toast: {} };
}),
}));
If it is possible to mock the loadable.lib function to render its children instead of wait for some library to be loaded, I don't know how I can fill the undefined variables that the code uses because I have loadables that use loadables that use loadables and so on.
I've read that there are some WebPack hints such as webpackPrefetch and webpackPreload but I am not sure if it is a good road to go.
Relevant links on what I have tried
The code I am working on (and there are 19 other files like this one): https://github.com/silviubogan/volto/blob/1d015c145e562565ecfa058629ae3d7a9f3e39e4/src/components/manage/Actions/Actions.jsx (I am currently working on loading react-toastify through loadable.lib always.)
https://medium.com/pixel-and-ink/testing-loadable-components-with-jest-97bfeaa6da0b - I tried to do a similar thing like the code in that article but it is not working:
jest.mock('#loadable/component', async (loadfn) => {
const val = await loadfn();
return {
lib: () => val,
};
});
A little bit of code
Taken from the link above, this is how I currently use react-toastify (named LoadableToast):
/**
* Render method.
* #method render
* #returns {string} Markup for the component.
*/
render() {
return (
<LoadableToast>
{({ default: toast }) => {
this.toast = toast;
return (
<Dropdown
item
id="toolbar-actions"
Conclusion
To put it in other words, how can I mock a dynamic import? How can I make jest go over lazy loading and provide a value instead of making the test wait to receive a value?
Thank you!
Update 1
With the following new code, still not working:
jest.mock('#loadable/component', (load) => {
return {
lib: () => {
let Component;
const loadPromise = load().then((val) => (Component = val.default));
const Loadable = (props) => {
if (!Component) {
throw new Error(
'Bundle split module not loaded yet, ensure you beforeAll(() => MyLazyComponent.load()) in your test, import statement: ' +
load.toString(),
);
}
return <Component {...props} />;
};
Loadable.load = () => loadPromise;
return Loadable;
},
};
});
A better solution is to mock the Loadable components module. We can do this simply by using Jest mock capabilities (https://jestjs.io/docs/manual-mocks#mocking-node-modules) and Loadable itself in the following way:
Create a #loadable directory inside the mocks folder at the root level of your project (mocks should be at the same level of node_modules).
Inside the #loadable folder, create a component.js file. In this file we will mock the loadable function by writing the code below:
export default function (load) {
return load.requireSync();
}
That's it, now you should be able to run your unit tests as normal.
We did it by
jest.mock('#loadable/component', () => ({
lib: () => {
const MegadraftEditor = () => {} // Name of your import
return ({ children }) => children({ default: { MegadraftEditor } })
},
}))
In case there is only one default export it could also be
jest.mock('#loadable/component', () => ({
lib: () => {
const HotTable = () => {} // Name of your import
return ({ children }) => children({ default: HotTable })
},
}))
Then in the test you just need to dive() and use childAt() until you have the correct location of the component you want to lazy-load. Note that this will then not have any of your wrappers around the lazy-loaded component in the snapshot.
it('renders correct', () => {
const component = shallow(
<View
data={}
/>
)
.dive()
.childAt(0)
.dive()
expect(component).toMatchSnapshot()
})

How to properly load global variable with async values(Reactjs)?

I'm trying to solve this problem that I can't seem to solve with stripe's API's
So when creating a charge with their new version API they say that in the front end we should call
loadStripe('publishable Key',{'Connected account ID'})
and set that to a const.
now I dont undestand how are we supposed to get the ID that is stored somewhere say a database?
As a reference please look at this and here (In Step 3 ...).
What I'm currently doing is something like this
import React from "react";
import ReactDOM from "react-dom";
import { Elements } from "#stripe/react-stripe-js";
import { loadStripe } from "#stripe/stripe-js";
import CheckoutForm from "./CheckoutForm";
//btw I have set this to const and to let and none work
const stripePromise = fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
anything: window.sessionStorage.getItem("Variable Account")
//here store something that will tell what account to pull ID data from
})
})
.then(data => data.json())
.then(result => {
return loadStripe("KEY", { stripeAccount: result });
});
class App extends React.Component {
render() {
return (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
}
}
export default App;
but the const seems to not load correctly if one goes with the regular flow of the app say from
myapp.com/home
-> click
myapp.com/home/something
-> then
myapp.com/home/something/payment
stripe is not loading but one refreshes the browser now works but that tells me I'm doing maybe something wrong or I have to make the app refresh in 'componentDidMount()' maybe?
One can set it to be static but connected accounts can be many so if anyone can help me with this I would appreciate it
Generally, you'd want to have this account ID available in your app. But if you need to retrieve it, that's fine, but make sure the stripePromise is what you think it is. For example, I can make this work here with a simulated fetch call here: https://codesandbox.io/s/stripe-connect-w-resolve-wts34
Note that I'm managing the Promise explicitly:
const stripePromise = new Promise((resolve, reject) => {
fetch(...)
.then(data => data.json())
.then(result => {
resolve(
loadStripe(STRIPE_PUBKEY, { stripeAccount: "acct_xxx" })
);
});
});
The fact that you describe this breaking with navigation suggests you might be routing incorrectly. If this is a single page app, the navigation shouldn't cause the App component to re-render.

Fetch global data for the whole app in Next.js on initial page load

In my Next.js application I have search filters.
Filters consist of checkboxes, and to render these checkboxes I need to fetch (GET) all possible options from the API.
Those filters are available on many pages, so regardless the page where user lands I need to fetch the data for filters immediately and put it in the local storage to avoid further excessive API calls. Putting API call in each page is not an option.
I see the one option is to put the API call in getInitialProps in _app.js, but then according to Next.js docs automatic static optimization will not work and every page in my app will be server-side rendered.
So what is the proper way to fetch such global data in Next.js?
--------UPDATE
So at this moment I've used the next solution: in _app.js I put useEffect React Hook and once the Frontend is ready I am checking whether my data for whole application is in locale storage. If it's not then fetch data from server and put in local storage for further use.
// _app.js
const AppWrapper = ({ children }) => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch({ type: FRONTEND_LOADED });
loadInitialData(dispatch);
}, [false]);
return <>{children}</>;
};
class MyApp extends App {
render() {
const { Component, router, pageProps } = this.props;
return (
<>
<AppProvider>
<AppWrapper>
<MainLayout pathname={router.pathname}>
<Component {...pageProps} />
</MainLayout>
</AppWrapper>
</AppProvider>
</>
);
}
}
// loadInitialData.js
import {
SET_DIETS_LIST,
UPDATE_FILTERS_FROM_CACHE,
} from "Store/types";
import fetch from "isomorphic-unfetch";
export default dispatch => {
const ls = JSON.parse(localStorage.getItem("filters"));
if (ls) {
const localStorageState = {
diet: {
list: ls.diet.list || [],
selected: ls.diet.selected || [],
},
...
};
dispatch({
type: UPDATE_FILTERS_FROM_CACHE,
payload: { filters: localStorageState },
});
}
if (!ls || !ls.diet.list.length) {
fetch(`${process.env.API_URL}/diets`)
.then(r => r.json())
.then(data => {
dispatch({ type: SET_DIETS_LIST, payload: { data[0] } });
});
}
...
};
It seems this filter is located on headermenu or sidebar menu?
If that is the case, I would suggest (an option other than _app.js) putting the API caller inside header/ sidebar component, and call the header/sidebar component on layout/ pages component.
Therefore, you will get the same behavior as what you've described (not invoking SSR on every pages and static optimization is still working because the concept is similar with the _app.js (just put it inside a structure).

React.js "global" component that can be created multiple times

I can't get my head wrapped around this.
The problem: let's say there's an app and there can be some sort of notifications/dialogs/etc that i want to create from my code.
I can have "global" component and manage it, but it would limit me to only one notification at a time, this will not fit.
render() {
<App>
// Some components...
<Notification />
</App>
}
Or i can manage multiple notifications by the component Notification itself. But state management will not be clear.
The other problem if i have some sort of user confirmation from that component (if it's a confirmation dialog instead of simple notification) this will not be very convinient to handle with this solution.
The other solution is to render a component manually. Something like:
notify(props) {
const wrapper = document.body.appendChild(document.createElement('div'))
const component = ReactDOM.render(React.createElement(Notification, props), wrapper)
//...
// return Promise or component itself
}
So i would call as:
notify({message: '...'})
.then(...)
or:
notify({message: '...', onConfirm: ...})
This solution seems hacky, i would like to let React handle rendering, and i have an additional needless div. Also, if React API changes, my code breaks.
What is the best practice for this scenario? Maybe i'm missing something completely different?
You could use React Context for this.
You create a React context at a high level in your application and then associate a values to it. This should allow components to create / interact with notifications.
export const NotificationContext = React.createContext({
notifications: [],
createNotification: () => {}
});
class App extends Component {
constructor() {
super();
this.state = {
notifications: []
};
this.createNotification = this.createNotification.bind(this);
}
createNotification(body) {
this.setState(prevState => ({
notifications: [body, ...prevState.notifications]
}));
}
render() {
const { notifications } = this.state;
const contextValue = {
notifications,
createNotification: this.createNotification
};
return (
<NotificationContext.Provider value={contextValue}>
<NotificationButton />
{notifications.map(notification => (
<Notification body={notification} />
))}
</NotificationContext.Provider>
);
}
}
The notifications are stored in an array to allow multiple at a time. Currently, this implementation will never delete them but this functionality can be added.
To create a notification, you will use the corresponding context consumer from within the App. I have added a simple implementation here for demonstration purposes.
import { NotificationContext } from "./App.jsx";
const NotificationButton = () => (
<NotificationContext.Consumer>
{({ notifications, createNotification }) => (
<button onClick={() => createNotification(notifications.length)}>
Add Notification
</button>
)}
</NotificationContext.Consumer>
);
You can view the working example here.

Categories