Script only works on page refresh in Gatsby/React - javascript

I'm trying to use a script from Tock in my Gatsby website which converts a "Reserve" button into a widget.
I have tried many different ways suggested here to make this work including the methods listed in this answer.
If I put the script in the component as shown below or in html.js, the code works but only if I refresh the page. If I navigate to the page from another page, it doesn't work.
If I place in an external JS file I get compiling errors.
What I thought should be simple is providing incredibly challenging so any help is greatly appreciated! Thanks
class ReserveTock extends React.Component {
render() {
return (
<>
<script dangerouslySetInnerHTML={{
__html:`
!function(t,o,c,k){if(!t.tock){var e=t.tock=function(){e.callMethod?
e.callMethod.apply(e,arguments):e.queue.push(arguments)};t._tock||(t._tock=e),
e.push=e,e.loaded=!0,e.version='1.0',e.queue=[];var f=o.createElement(c);f.async=!0,
f.src=k;var g=o.getElementsByTagName(c)[0];g.parentNode.insertBefore(f,g)}}(
window,document,'script','https://www.exploretock.com/tock.js');
tock('init', 'websitename');
`}}
/>
<a className = {"btn " + this.props.buttonColor + ' ' + this.props.className}
href="https://www.exploretock.com/websitename"
data-tock-reserve="true"
data-tock-experience={this.props.experienceTockId}
>
Reserve
</a>
</>
)
}
}

In my experience: third party scripts that you load into your page need to go as high up in the HTML as possible. So put the script tag directly in the HTML head, don't inject it with JavaScript :)

Related

How to open some child links in new tab in React

I have a page built with React (NextJS) and I am pulling some markup string content from Wordpress and inserting it into my JSX, like so:
...
<div className="wrapper">
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
Now, the markup possibly contains links and I want to open all those links on new tab. So I tried:
...
<div className="wrapper">
<base target="_blank" />
<p
className="text-content"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
></p>
</div>
...
and all links in the markup are opened on new tab so, great. But the problem is that all other links in the page including those outside the div.wrapper element are opened in new tabs (since <base /> is scoped to the entire page) and I'll like to prevent this.
Since I can't use multiple <base /> on the same page, the other option I'm aware of is to loop through anchor tags of interest with document.querySelector(".wrapper a") and add the target attribute to all of them but, in React it's an anti-pattern to modify the DOM directly.
So I'm not sure how best to proceed. What do I do?
You can use DOMParser API to achieve that.
Here's a little snippet
const parser = new DOMParser();
const htmlText = `<div>Url link</div>`;
let content = parser.parseFromString(htmlText, "text/html");
const anchors = content.getElementsByTagName('a');
Array.from(anchors).forEach(a => {
a.setAttribute("target", "_blank");
})
console.log(content.body.innerHTML); // Here it is your new string
The code may need to be improved a bit, I've just typed this out of MDN example and I didn't have time to test it. Does this work?
Well first of all base element should only be inserted in html head element, and not inside the body html element, you could do that imperatively or using the react-helmet library - if you still need to use it.
dangerouslySetInnerHTML is in itself an imperative pice of code, but sometimes its the only possible solutions for a certain use cases, now regarding the links you could either do it using imperative code in a useEffect or componentDidMount, or you code use react-html-parser which will enable you to modify dom elements in a more declarative fashion - i say in a more declarative fashion because while its a react component in practice its still more imperative than its declarative in nature, but still better than custom code running in useEffect or componentDidMount

How can I add a script function to React

I am trying to add an external application Chameleon onto my react application and for that I have to add the javascript function to my application.
I only want it to be called in specific situations so I don't want to load it in my index.html. I tried adding it to the render function of my component as:
render() {
return(
<div>
<head>
<script type="text/javascript">/* Chameleon - better user onboarding */!function(t,n,o){var a="chmln",c="setup identify alias track clear set show on off custom help _data".split(" ");n[a]||(n[a]={}),n[a].accountToken=o,n[a].location=n.location.href.toString();for(var e=0;e<c.length;e++)!function(){var t=n[a][c[e]+"_a"]=[];n[a][c[e]]=function(){t.push(arguments)}}();var s=t.createElement("script");s.src="https://fast.trychameleon.com/messo/"+o+"/messo.min.js",s.async=!0,t.head.appendChild(s)}(document,window,"TOKEN");
chmln.identify(USER.ID_IN_DB, { // Unique ID of each user in your database (e.g. 23443 or "590b80e5f433ea81b96c9bf6")
email: USER.EMAIL });
</script>
...
...
</head>
</div>
)
}
But the above doesn't seem to work. I tried the same inside a helmet but no luck. Both of them show an error for
SyntaxError: Unexpected token
Is there a way I can load this function in a specific component or do I have to do it in the index.html?
You seem to have a strong misunderstanding of what react is for and how it is used.
1) There should only ever be 1 head element on the page, and it should be in index.html not in the rendered output of a component.
2) Having a component render a <script> tag goes against the point of using react.
What you need to do is import the code you need into your component:
import './path/to/file.js'
And then from there chmln should be available on the window object
window.chmln.identify()

React: How to use same component twice in same page but loading one script tag for both just one time

I created a react component that I want to use twice(or more) inside my page, and I need to load a script tag for it inside the head of my page but just once! I mean even if I use the component twice or more in the page it should add the script tag just once in the head.
The Problem is that this script tag should be absolutely a part of the component and not statically inserted in the head of my page.
Can anyone help me to make the magic happens? Thanks a lot in advance!
You can give react-helmet a try for managing changes to your <head> from within React components.
In particular, you can check this example where rendering the same element four times only adds the script tag once.
For completeness, the relevant code from the example (although the interesting part is to see how it executes):
import { Helmet } from "react-helmet";
function ComponentWithHeader() {
return (
<div>
<div>Oh hi</div>
<Helmet>
<script src="fake-url.js" />
</Helmet>
</div>
);
}
const App = () => (
<div>
<ComponentWithHeader />
<ComponentWithHeader />
<ComponentWithHeader />
<ComponentWithHeader />
</div>
);
You can set the state of the parent component to keep in memory that the script is already added.
if (!this.state.scriptAdded) {
// Add script tag
this.setState({ scriptAdded: true });
}

How to correctly reference 'nested' tag with each={} in riotjs

I'm trying to work in the concept of a loading tag I can wrap other elements to give a consistent loading display when retrieving data async. Consider the following code:
This example relies on browserify (require) but shouldn't make a difference to
the question
<test>
<loading>
<ul>
1. = <li each={ items }>{ title }</li>
or
2. = <li each={ parent.items }>{ title }</li>
or
3. = <li each={ opt.data.items }>{ title }</li>
</ul>
</loading>
<script>
require('riot');
require('./loading.tag');
this.items = [
{ title: 'title 1'},
{ title: 'title 2'}
];
this.on('mount', function () {
riot.mount('loading', /* for 3 = */ {data: this.items});
})
</script>
</test>
As you can see, the tag is <test /> and contains a nested tag <loading /> which wraps the content <test /> displays. Problem is I'm unsure of the correct way to reference the items array (which would in the real world be pulled in via ajax). I tried options 1 & 2 but nothing would display. 3 works (passing the data as opts) but doesn't feel right.
It may have something to do with <yield /> which is how <loading /> is displaying its contents but I don't know why.
<loading>
<div><yield /></div>
</loading>
When I tried your above code 2 got the correct result. I have a few issues with your above code that may be causing you issues.
1) I've never used require inside of a tag. I doubt it works when requiring a tag file. The .tag extension does nothing. It's the type="riot/tag" that signals a script tag is not javascript but a special script that can be used by riot.
2) You've closed the tag </about> instead of </test>. I think your riot tag just won't compile under these circumstances.
3) I don't know what version of riot you're using, but in 2.3.12 if you mount the test tag then any children (in this case your "loadings") will automatically mount provided you have already loaded the .tag file. I think the problem is that you're calling mount on your "loading" tag twice, which may divorce them from their parent.
I think your issue is that the <loading> tag means nothing when <test> is mounted. You then require loading.tag, which then allows loading to be mounted. Try requiring <loading> before you mount test. I made a js fiddle with how I would do it. Hopefully this answers your question.
https://jsfiddle.net/cm09mtc5/

Kendo UI - Localize application

How does one localize a pure front-end application that uses a framework such as Kendo UI ?
I mean, it's possible to do something like this:
$(document).ready(function(){
$("#myText").html(<grab text based on language>);
});
But then, if I have a listview and want to localize its title:
<div id="tabstrip-expenseaccounts" data-role="view">
<ul data-role="listview" data-style="inset" data-type="group">
<li id="expenseaccounts-listview-title">
abcde
<ul>
...
</ul>
</li>
</ul>
</div>
Becomes:
...
<li id="expenseaccounts-listview-title" class="km-group-container">
<div class="km-group-title">
<div class="km-text">abcde</div>
</div>
<ul class="km-list">
...
</ul>
</li>
...
I need to inspect the generated code and do something like:
$(document).ready(function(){
$("#expenseaccounts-listview-title.km-group-container div.km-group-title div.km-text").html(<grab text based on language>);
});
It works fine, but that doesn't seem like a clean solution to me.
Any advice ? Thanks!
For KendoUI there some language packs available on GitHub here. This other stakoverflow question should give you a headstart. With this, all you have to do is use the correct language pack and you're good to go. And if there is no language pack for your specific case, you can always roll your own.
Hope this helps.
While I have not found a solution proper to Kendo UI, here is the approach I went for to localize my mobile application. Note here that I am not talking about localizing widgets, I am referring to localizing every static aspect of the application: input placeholders, texts on buttons, headings, etc.
My mobile application only has one file, index.html, and whenever I want to navigate to a different page, i simply move to a different view. Since having multiple views in the same file is kind of a mess, I made one html file per view, and am dynamically loading them into the body (index.html has an empty body). Before appending the html which is retrieved using $.get for each view (at this point, it's a huge string), i am replacing text based on the current language (which is retrieved from the localstorage/cookie or from a default value).
example:
In my localization library:
_localization.localizeText = function(text, arr){
arr.forEach(function(item){
text = text.replace(item.name, getLang() == 1 ? item.replacement.en : item.replacement.fr);
});
return text;
}
In my login.html file:
<button>$$login-button$$</button>
And then in some javascript file which is included before the script in which the application is initialized:
var replacements = [];
replacements.push({
name: "$$login-button$$",
replacement: {
fr: "Connecter",
en: "Log In"
}
});
And then when i'm loading my files into the body:
$.when($.get("login.html"))
.done(function(p1){
var body = $("body");
body.append(localization.localizeText(p1[0], app.replacements));
});
Hope this helps anyone with similar issues!

Categories

Resources