Create BuyMeACoffee Component for Gatsby JS - javascript

I am trying to create a Buy Me A Coffee React component for my Gatsby website and even though my Gatsby site runs in development mode and successfully builds, the component (Buy Me A Coffee widget) doesn't show up when loading the page.
My website uses MDX, so ideally I'd like to be able to import the component into my blog posts. I like the idea of importing it into my blog posts because it allows me to optionally include it, whereas if I use a standard solution like gatsby-ssr.js for including the third party Buy Me A Coffee script, I foresee it being much more difficult to regulate on what pages the component does and doesn't show.
At the moment, I use the library browser-monads so I don't have to do typeof !== "undefined" conditional check for building my site. Using the traditional conditional format they recommend here doesn't help. Also styles.scss is currently empty. I am importing this in case I need to go back and add styles to my component later.
Thanks for your help!
Below is my code:
import React from 'react';
import './styles.scss'
import { window, document, exists } from 'browser-monads';
class BuyMeACoffee extends React.Component {
constructor(props){
super(props)
let script = document.createElement("script");
script.setAttribute('data-name','BMC-Widget')
script.src = "https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js"
script.setAttribute('data-id', 'x');
script.setAttribute('data-description', 'Thank you for your support!');
script.setAttribute('data-message', "We're proudly reader-supported! If our content helps you, we would be honored and greatly appreciate it if you'd consider buying us a coffee!");
script.setAttribute('data-color',"#2962ff")
script.setAttribute('data-position','right')
script.setAttribute('data-x_margin','18')
script.setAttribute('data-y-margin','18')
script.async = true
//Call window on load to show the image
script.onload=function(){
var evt = document.createEvent('Event');
evt.initEvent('load', false, false);
window.dispatchEvent(evt);
}
this.script=script
}
componentDidMount () {
document.head.appendChild(this.script)
}
componentWillUnmount(){
document.head.removeChild(this.script);
document.body.removeChild(document.getElementById("bmc-wbtn"))
}
render(){
return(null)
}
}
export default LoadBuyMeACoffee;

I would suggest using gatsby-ssr.js approach instead of adding a load performance directly in the class component. Something like this should work for you:
const React = require("react");
exports.onRenderBody = ({ setPostBodyComponents }) => {
setPostBodyComponents([
<script
data-name="Mac-Mann-Widget"
src="https://cdn.buymeacoffee.com/widget/1.0.0/prod/widget.prod.min.js"
data-id="eshlox"
data-description="Support me on Buy me a coffee!"
data-message="Thank you for visiting. You can now buy me a coffee!"
data-color="#FF813F"
data-position="right"
data-x_margin="28"
data-y_margin="18"
></script>,
]);
};
I will leave the snippet above to see if it helps someone.
Thanks for the response. Unfortunately I really don't want to use
gatsby-ssr.js because I want the ability to be able to optionally
include the Buy Me A Coffee script in my blog posts. – Mac-Mann 4
hours ago
You can try something like this:
Create a function that will render asynchrounously on-demand the script:
const addExternalScript = (url, callback) => {
const script = document.createElement('script');
script.src = url;
script.async=true;
script.onload = callback;
// add as many parameters as you need.
document.body.appendChild(script);
};
You can also remove the callback function parameter if you don't need it despite being useful.
Call them in your componentDidMount() (that will make the trick since you need to wait for the DOM tree loading until access to a document global object):
componentDidMount(){
addExternalScript(`https://cdn.buymeacoffee.com/widget/1.0.0/prod/widget.prod.min.js`)
}

Related

How to properly run a third party script in react?

I have a need in a React application to add a support chat made in bitrix - this is a script that is usually added to the index.html file.
example script:
<script>
(function(w,d,u){
var s=d.createElement('script');s.async=true;s.src=u+'?'+(Date.now()/60000|0);
var h=d.getElementsByTagName('script')[0];h.parentNode.insertBefore(s,h);
})(window,document,'https://someurl.js');
</script>
At the moment, I have implemented the connection as follows:
created a component with a script
import React, { Component } from 'react'
export default class Script extends Component {
componentDidMount() {
const s = document.createElement('script')
// s.type = 'text/javascript'
s.async = true
s.innerHTML = "document.write('This is output by document.write()!')"
s.src =
'https://someurl.js' +
'?' +
((Date.now() / 60000) | 0)
this.instance.appendChild(s)
const h = document.getElementsByTagName('script')[0]
h.parentNode.insertBefore(s, h)
}
render() {
return <div ref={(el) => (this.instance = el)} />
}
}
and connected it at the root of the application
import Script from './Script '
const App = () => {
return(
<>
<App />
<Script />
</>
)
}
It works, but I think this solution is not the most correct one. If Someone can help to run external script correctly in reactive applications (react, vue) I would be very grateful.
thanks in advance!!
Putting it in index.html is fine and the most compliant approach.
The only reasons to not do that would be:
Embedding the script is conditional - it depends on something you only know as soon as the React/Vue/etc. app is running. Or you want to delay the embedding.
You're hooking something like a Promise into the loading of the script, for example checking when the Bitrix chat is up and running
You need to pass values only known to the React/Vue/etc. app to the embedding of the script (api key for example)
Since none of these seem to be the case here, I'd keep it in index.html.
I remember there was a property called dangerousSetInnerHTML that you could look up on the React official website.

Use custom JavaScript code in a Vue.js app

I'm trying to insert JavaScript code in a Vue.js router app. I need to load data from the CMS the app is served from. In order to get the data from the CMS I have to use a JavaScript library from the CMS which is not made for Vue and is not exporting it's class/functions like modern JS. So I import the JS library from in the index.html by a script tag. This works as intended.
But now I have to use the class from this CMS JavaScript library.
Before writing this as a Vue-Router app I just have used Vue for templating purposes.
So I had some code packed in the window.onload event handler.
I have to create an instance for the CMS data access class.
But this leads to a build error (using vue-cli build). Since there
are no understandable error messages from the build process
I have to use trial and error. Even simple variable assignments like var a = 1 seem not to be allowed.
A console.log('something') works. But nothing else seemes to be allowed (except defining the onload-event handler)
I have added this code in a <script> Tag inside App.vue (which was created by vue-cli create)
window.onload = function() {
try {
// Instantiate class obj for CMS data access
cmsDataAccessObj = new CMSAccessData();
waitForPlayerData = true;
}
catch (e) {
console.error(e);
}
}
UPDATE
After testing the different solutions from the answers I got aware that using non-instance variables seems to cause the build errors.
This gives an error:
waitForPlayerData = true;
This works:
this.waitForPlayerData = true;
I wouldn't recommend using window.load to run your code. There are more native approaches to do this in Vue.js.
What you should do in the case you want to run it in the main component of the app before it's been loaded is to put the code inside the beforeCreate lifecycle hook of the main component.
...
beforeCreate () {
this.cmsDataLoader()
},
methods: {
cmsDataLoader () {
try {
// Instantiate class obj for CMS data access
cmsDataAccessObj = new CMSAccessData();
waitForPlayerData = true;
}
catch (e) {
console.error(e);
}
}
}
...
This will run the code everytime a component is created before the creation. You could also use the created lifecycle hook if you want to run it after the creation of the component.
Check the following link for more information about lifecycle hooks.
The best way to place JavaScript in Vue.js App is mounted function, it is called when the component is loaded:
export default {
name: "component_name",
mounted() {
let array = document.querySelectorAll('.list_item');
},
}
You don't need window.onload, you can just put whatever you want there. I'm not entirely certain when precisely in the lifecycle it renders and maybe someone can hop in and let us know but it for sure renders when page starts. (though it makes sense that it does before the lifecycle hooks even start and that it'll solve your needs)
Better & easier solution if you want to load it before Vue loads is to add it to the main.js file. You have full control there and you can load it before Vue initializes.
No need for window.onload there either, just put it before or import a JS file before you initialize Vue, because it's going to be initialized by order.

React: Programmatically opening modals (and cleaning up automatically)

I have a site with lots of modals which can be opened from anywhere (for example LoginModal). The challenge I'm running into is if I open one programmatically with something like ReactDOM.render, how do I clean it up automatically when the parent component is unmounted without putting it (and all possible modals) in the template.
For example, something like this to open it:
openLoginModal() {
ReactDOM.render(<LoginModal />, document.body);
}
LoginModal can clean itself up when closed. However, if the DOM from the component which opened it is unmounted, how do I let LoginModal know to unmount as well.
One thought I've had is to use an Rx.Subject to notify it when to unmount, but this also sounds like a bit of a wrong approach and a possible anti-pattern.
For example:
// modules/User.js
openLoginModal(unmountSubj) {
const container = document.createElement('div');
ReactDOM.render(<LoginModal />, container);
unmountSubj.subscribe(() => {
ReactDOM.unmountComponentAtNode(container);
});
}
// components/RandomView.jsx
unmountSubject = new Rx.Subject();
componentWillUnmount() {
this.unmountSubject.next();
}
login() {
User.openLoginModal(this.unmountSubject);
}
I'd like to avoid having all the possible modal components in each JSX template they might be used in.
How would you approach this?
Here's the solution I've come up with so far: There's a modal manager module, which will render a modal into the DOM (via ReactDOM.render) and return a function which will unmount it.
Here's a simplified version:
// modalManager.js
export default modalManager = {
openModal(modalClass, props) {
// Create container for the modal to be rendered into
const renderContainer = document.createElement('div');
document.body.appendChild(renderContainer);
// Create & render component
const modalInst = React.createElement(modalClass, { ...props, renderContainer });
ReactDOM.render(modalInst, renderContainer);
// Return unmounting function
return () => this.unmountModal(renderContainer);
},
unmountModal(renderContainer) {
ReactDOM.unmountComponentAtNode(renderContainer);
renderContainer.parentNode.removeChild(renderContainer);
},
}
// TestThing.jsx
class TestThing extends React.Component {
unmountLogin = null;
componentWillUnmount() {
this.unmountLogin();
}
login() {
this.unmountLogin = modalManager.openModal(Login, {});
}
}
You'll also notice that renderContainer is passed to the modal component. This way the modal can call modalManager.unmountModal itself when closed.
Let me know what you think.
In my current React project, I've addressed this by having a multilayered component architecture.
<App>
// the standard starting point for React apps
<DataLayer>
// this is where I make API calls for data that is shared between all components
<DisplayLayer>
// this is where I put the methods to launch display elements that are shared
// by all components (e.g., modals, alerts, notifications, etc.)
<Template>
// this is the first layer that is actually outputting HTML content
<ModuleX>
<ModuleY>
<ModuleZ>
// these modules control the main display area of the screen, they encompass
// major UI functions (e.g., UsersModule, TeamsModule, etc.)
// when one of these modules needs to launch a shared UI element (like a
// modal), they call a method in the <DisplayLayer> template - this means
// that if I have a commonly-used modal (like, LoginModal), it doesn't need
// to be included in every one of the core modules where someone might need
// to initiate a login; these modules are mounted-and-unmounted as the user
// navigates through the app
So when the app loads, <App>, <DataLayer>, <DisplayLayer>, and <Template> all load up (and they will only load one time). As the user navigates around, the <ModuleX/Y/Z/etc> components are mounted-and-unmounted, but all of the "common stuff" stays in place that was mounted/loaded in the higher layers of the app.

Is componentDidMount causing html.script to disappear?

I am having issues mounting an external script into a component of my React/Gatsby App. The script below is called into a component that is used in two places throughout app.
First being pages/index.js and loads fine with zero issue, yet when called to use within a gatsby created page (exports.createPages = ({ graphql, boundActionCreators }) => {) from a template the script will load, show content and then go.
Here is the code for the script being mounted into the component -
componentDidMount () {
const tripadvisor = document.createElement("script");
tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
tripadvisorLeft.async = true;
document.body.appendChild(tripadvisor);
}
I am not getting any errors from the console.
NOTE: Incase of relation to the error? I also have this code using componentDidMount and componentWillUnmount in the /layout/index.js file that handles a body class for navigation elements.
componentDidMount () {
this.timeoutId = setTimeout(() => {
this.setState({loading: ''});
}, 100);
this.innerContainer.addEventListener("scroll", this.handleHeaderStuck), 100;
this.innerContainer.addEventListener("scroll", this.handleSubNavStuck), 200;
}
componentWillUnmount () {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.innerContainer.removeEventListener("scroll", this.handleHeaderStuck);
this.innerContainer.removeEventListener("scroll", this.handleSubNavStuck);
}
UPDATE: All code
import React from 'react';
import Link from 'gatsby-link'
import styled from 'styled-components'
const Wrapper = styled.section`
display:block;
`
class ReviewsPage extends React.Component {
componentDidMount () {
const tripadvisorLeft = document.createElement("script");
tripadvisorLeft.src = "https://www.jscache.com/wejs?wtype=selfserveprop&uniq=789&locationId=10467767&lang=en_NZ&rating=true&nreviews=0&writereviewlink=true&popIdx=true&iswide=true&border=false&display_version=2";
tripadvisorLeft.async = true;
document.body.appendChild(tripadvisorLeft);
}
render() {
return (
<Wrapper id="tripAdvisor">
<div id="TA_selfserveprop789" className="TA_selfserveprop">
<ul id="3LacWzULQY9" className="TA_links 2JjshLk6wRNW">
<li id="odY7zRWG5" className="QzealNl"></li>
</ul>
</div>
</Wrapper>
)
}
}
export default ReviewsPage
So, all your componentDidMount() is doing is adding a <script> tag which references a third party script. I am assuming that third party script tries to add some information or thing to the DOM (something you can see visually).
However, the DOM only exists between component updates. React will completely redraw the DOM (the HTML inside your component) any time it detects a change to State or Props. I'm assuming in this case that Wrapper is what is resetting each time.
I'm not sure how to help with this, mainly because React's entire role in an application is really just managing the state of the DOM, and that script is trying to edit the DOM, but without telling React. React might be sensing an invalid change to the DOM then trying to correct it, but I really don't think React does that. At any rate, the issue is that React is trying to manage the DOM while another thing is trying to edit the DOM, and that's not gonna end well.
It would be better if you could have a script that asynchronously calls to the other service and receives data, then let React apply that data to the DOM, instead of letting the script edit the DOM itself. Granted, you probably don't have control over how that external script actually works, which is why I say I'm not sure how to help.

Advice about webapp architecture and reactJs

This question is more to know your opinions about the way I'm trying to solve this issue.
I would need a little bit of ReactJs expertise here as I'm quite new.
First a little bit of context. I'm developing a web application using ReactJs for the frontend part.
This webapp is going to have many translations, so for maintenance I thought it would be better to store all the translations in a database instead of having them into a file. This way I could manage them using sql scripts.
I'm using a MySQL database for the backend, but for performance reasons, I have added ElasticSearch as second database (well, it is more a full text search engine).
So once the application starts, the translations are automatically loaded into ElasticSearch. Every translation has a code, and a text, so in elastic search I only load the translations for one locale (by default english), and when a user switchs the locale, a call is done to load all the translations for the selected locale and update their corresponding text.
This way from the fronted I can reference a translation only by the code and I will get the text translated in the correct locale.
Now, how do I do that in react?
So far I have written a component TranslatedMessage which is basically looking for a given code and displaying it whereever this component is rendered.
Below the code of the component:
import React from 'react';
export class TranslatedMessage extends React.Component {
constructor() {
super();
this.render = this.render.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.state = {message: ''};
}
render() {
return (<div>{this.state.message}</div>);
}
componentDidMount() {
var component = this;
var code=this.props.code;
var url="data/translation?code="+code;
$.get(url, function (result) {
component.setState({message: result.text});
});
}
};
And then I use it in the application whis way, for example to translate the title of an 'a' link:
<TranslatedMessage code="lng.dropdown.home"/><i className="fa fa-chevron-down" />
So far is working fine but the problem is that I need to refresh the whole page to get the new translations displayed, because I'm not updating the state of the component.
So now my questions:
1)Every time that we find in a page the component TranslatedMessage, a new instance of that component is created right? so basically if I have 1000 translations, 1000 instances of that component will be created? And then React has to take care and watch all these instances for changes in the state? Would that be very bad for performance? Do you find any more efficient way to do it?
2) I don't think forcing the whole page to reload is the most proper way to do it, but how can I update the states of all that components when a user switch the locale? I've been reading about a framework (or pattern) called Flux, and maybe that could fit my needs, what do you thing, would you recommend it?
3) What do you think about storing translations on db, I'm a bit concern about sending a query to the db for every translation, would you recommend or not this approach?
Any suggestions, ideas for improvement are very welcome!
Thank you for taking your time to read it or for any help!
I use what is basically a Flux store for this purpose. On initialisation the application requests the whole language file to use (which is JSON) and that gets shoved into memory in the store, something like this (I'm going to assume a totally flat language file for now, and I'm using ES2015/16 syntax, I've omitted error checking etc etc for brevity):
class I18n {
constructor() {
this.lang = await fetch( 'lang_endpoint' )
.then( res => res.json() )
}
get( translation ) {
return this.lang[ translation ] || null
}
}
Somewhere my app starts during a ReactDOM.render( <App /> ) or some variation and this renders the whole thing top-down (I try to eliminate state as much as possible). If I needed to switch languages then I'd bind a change handler such that the store emits a change event which is heard by some code that triggers a ReactDOM.render. This is fairly standard Flux practise for changing the app state, the key is to try and eliminate state from your components and store it inside your stores.
To use the I18n class simply instantiate it somewhere (I normally have it as a singleton exported from a file, e.g. module.exports = new I18n(), require that file into your components and use the get method (this assumes some sort of packager such as browserify or webpack but it looks like you have that complexity all sorted):
import 'i18n' from 'stores/i18n'
class MyComponent extends React.Component {
constructor() { ... }
render() {
return (
<span>{ i18n.get( 'title' ) }</span>
)
}
}
This component could also be simplified to
const MyComponent = props => <span>{ i18n.get( 'title' ) }</span>

Categories

Resources