I am trying to implement GTM with reactjs. I have used react-google-tag-manager but it did not solve the purpose.
Somehow, the data layer needs to be in a particular format and also the needs to be right below the tag, but it is only one of them that i can achieve at a time.
I tried placing the code directly in template.html and call the function from the component i wanted, but that didn't work.
import React from 'react';
import gtmParts from 'react-google-tag-manager';
class GoogleTagManager extends React.Component {
componentDidMount() {
const dataLayerName = this.props.dataLayerName || 'dataLayer';
const scriptId = this.props.scriptId || 'react-google-tag-manager-gtm';
if (!window[dataLayerName]) {
const gtmScriptNode = document.getElementById(scriptId);
eval(gtmScriptNode.textContent);
}
}
render() {
const gtm = gtmParts({
id: this.props.gtmId,
sourcegroup: this.props.gtmGroupname,
sourceid:this.props.gtmSource,
age:this.props.age,
mtongue:this.props.gtmMtongue,
city:this.props.city,
});
return (
<div>
<div>{gtm.noScriptAsReact()}</div>
<div id={this.props.scriptId || 'react-google-tag-manager-gtm'}>
{gtm.scriptAsReact()}
</div>
</div>
);
}
}
export default GoogleTagManager;
I am pushing parameters in DataLayer and on checking on google tag assistant addon, whole the datalyer is empty.
I had this issue yesterday and to solve it, I had to put all the properties that I'm trying to record under the additionalEvents property. Something like this:
const gtm = gtmParts({
id: this.props.gtmId,
additionalEvents: {
sourcegroup: this.props.gtmGroupname,
sourceid:this.props.gtmSource,
age:this.props.age,
mtongue:this.props.gtmMtongue,
city:this.props.city
}
})
And also avoid using eval() since this is a dangerous pratique. Update your code like this:
if (!window[dataLayerName]) {
const script = document.createElement("script")
const gtmScriptNode = document.getElementById(scriptId)
const scriptText = document.createTextNode(gtmScriptNode.textContent)
script.appendChild(scriptText)
document.head.appendChild(script)
}
Related
I'm trying to create a custom hook which will eventually be packaged up on NPM and used internally on projects in the company I work for. The basic idea is that we want the package to expose a provider, which when mounted will make a request to the server that returns an array of permission strings that are then provided to the children components through context. We also want a function can which can be called within the provider which will take a string argument and return a boolean based on whether or not that string is present in the permissions array provided by context.
I was following along with this article but any time I call can from inside the provider, the context always comes back as undefined. Below is an extremely simplified version without functionality that I've been playing with to try to figure out what's going on:
useCan/src/index.js:
import React, { createContext, useContext, useEffect } from 'react';
type CanProviderProps = {children: React.ReactNode}
type Permissions = string[]
// Dummy data for fake API call
const mockPermissions: string[] = ["create", "click", "delete"]
const CanContext = createContext<Permissions | undefined>(undefined)
export const CanProvider = ({children}: CanProviderProps) => {
let permissions: Permissions | undefined
useEffect(() => {
permissions = mockPermissions
// This log displays the expected values
console.log("Mounted. Permissions: ", permissions)
}, [])
return <CanContext.Provider value={permissions}>{children}</CanContext.Provider>
}
export const can = (slug: string): boolean => {
const context = useContext(CanContext)
// This log always shows context as undefined
console.log(context)
// No functionality built to this yet. Just logging to see what's going on.
return true
}
And then the simple React app where I'm testing it out:
useCan/example/src/App.tsx:
import React from 'react'
import { CanProvider, can } from 'use-can'
const App = () => {
return (
<CanProvider>
<div>
<h1>useCan Test</h1>
{/* Again, this log always shows undefined */}
{can("post")}
</div>
</CanProvider>
)
}
export default App
Where am I going wrong here? This is my first time really using React context so I'm not sure where to pinpoint where the problem is. Any help would be appreciated. Thanks.
There are two problems with your implementation:
In your CanProvider you're reassigning the value in permissions with =. This will not trigger an update in the Provider component. I suggest using useState instead of let and =.
const [permissions, setPermissions] = React.useState<Permissions | undefined>();
useEffect(() => {
setPermissions(mockPermissions)
}, []);
This will make the Provider properly update when permissions change.
You are calling a hook from a regular function (the can function calls useContext). This violates one of the main rules of Hooks. You can learn more about it here: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions
I suggest creating a custom hook function that gives you the can function you need.
Something like this, for example
const useCan = () => {
const context = useContext(CanContext)
return () => {
console.log(context)
return true
}
}
Then you should use your brand new hook in the root level (as per the rules of hooks) of some component that's inside your provider. For example, extracting a component for the content like so:
const Content = (): React.ReactElement => {
const can = useCan();
if(can("post")) {
return <>Yes, you can</>
}
return null;
}
export default function App() {
return (
<CanProvider>
<div>
<h1>useCan Test</h1>
<Content />
</div>
</CanProvider>
)
}
You should use state to manage permissions.
Look at the example below:
export const Provider: FC = ({ children }) => {
const [permissions, setPermissions] = useState<string[]>([]);
useEffect(() => {
// You can fetch remotely
// or do your async stuff here
retrivePermissions()
.then(setPermissions)
.catch(console.error);
}, []);
return (
<CanContext.Provider value={permissions}>{children}</CanContext.Provider>
);
};
export const useCan = () => {
const permissions = useContext(CanContext);
const can = useCallback(
(slug: string) => {
return permissions.some((p) => p === slug);
},
[permissions]
);
return { can };
};
Using useState you force the component to update the values.
You may want to read more here
I need to call an api which consists of an array of string. I need to then publish the response from the api in a dropdown menu. Below is what the API holds that I need to call-
Sample api data - [“Leanne Graham”,”Ervin Howell”,”Patricia”]
Below sample code has the API which holds object information
import React, { Component } from "react";
import "../styles/schema.css";
import Params1 from "../components/Params1";
import axios from 'axios';
import Select from "react-select";
class New extends Component {
constructor(props) {
super(props);
this.handleStoreprocSelection = this.handleStoreprocSelection.bind(this);
this.state = {
selectStoreprocOptions : [],
id: "",
name: '',
itemSelected:false
}
}
async getStoreProcOptions(){
const resSchema = await axios.get('https://jsonplaceholder.typicode.com/users') --backend API call in object format
const data = resSchema.data
const options = data.map(d => ({
"value" : d.id,
"label" : d.name
}))
this.setState({selectStoreprocOptions: options})
}
handleStoreprocSelection(){
// alert('You selected me!!')
this.setState({itemSelected: true});
}
componentDidMount() {
// get all entities - GET
this.getStoreProcOptions()
}
render() {
const itemSelected = this.state.itemSelected;
let param;
if (itemSelected) {
param = <Params1 />;
}
return (
<div>
<div>
<form id ="store-proc">
<label>STORED PROCEDURE</label>
<Select id="store-proc-select" options={this.state.selectStoreprocOptions} onChange={this.handleStoreprocSelection} /> --my dropdown
</form>
</div>
{param}
</div>
);
}
}
export default New;
You need a state, let's say an empty array.
You need to call that API, using some of the methods, for example browser built in fetch or 3rd party library axios.
Then you need to update your state with the response you will get from your API.
Finally use your state inside of your component and display whatever you want.
These are the steps you need to follow, if you needed some logic. Since you didn't provide any code, I assume you didn't know from where to start. If you share some code, will be possible to help more.
are you using any library? because the plain HTML form select would be written in lower case <select/>, not <Select/>. if so, please state it out.
in plain HTML: the solution would be to map the array elements into <option/>. so, selectStoreprocOptions from state, as assigned here: this.setState({selectStoreprocOptions: options}).
inside render:
<select>
{
this.state.selectStoreprocOptions.map(selectStoreprocOption => (<option ..> .. </option>)
}
</select>
Edit: Sorry, I've overseen the use of react-select. never used it, according to the API doc it looks good to me. have you checked that your state really contains an array with the expected objects?
probably ignore my post then, sorry again xD
I am trying to create a menu component that reads the contents of the pages folder at build time. However I haven't had any success. Here is what I have tried:
import path from "path";
import * as ChangeCase from "change-case";
export default class Nav extends React.Component {
render() {
return (
<nav>
{this.props.pages.map((page) => (
<a href={page.link}>{page.name}</a>
))}
</nav>
);
}
async getStaticProps() {
let files = fs.readdirSync("../pages");
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(file);
return stat.isFile();
});
const pages = files.map((file) => {
if (file == "index.js") {
const name = "home";
const link = "/";
} else {
const link = path.parse(file).name;
const name = ChangeCase.camelCase(link);
}
console.log(link, name);
return {
name: name,
link: link,
};
});
return {
props: {
pages: pages,
},
};
}
}
This does not work, the component does not receive the pages prop. I have tried switching to a functional component, returning a promise from getStaticProps(), switching to getServerSideProps(), and including the directory reading code into the render method.
The first two don't work because getStaticProps() and getServerSideProps() never get called unless the component is a page, and including the code in the render method fails because fs is not defined or importable since the code might run on the front end which wouldn't have fs access.
I've also tried adding the code to a getStaticProps() function inside _app.js, with the hopes of pushing the pages to the component via context, but it seems getStaticProps() doesn't get called there either.
I could run the code in the getStaticProps function of the pages that include the menu, but I would have to repeat that for every page. Even if I extract the logic into a module that gets called from the getStaticProps, so something like:
// ...
export async function getStaticProps() {
return {
props: {
pages: MenuMaker.getPages(),
// ...
}
}
}
and then pass the pages to the navigation component inside the page via the Layout component:
export default function Page(props) {
return (
<Layout pages={props.pages}></Layout>
)
}
then that's still a lot of boilerplate to add to each page on the site.
Surely there is a better way... It can't be that there is no way to add static data to the global state at build time, can it? How do I generate a dynamic menu at build time?
I managed to get this working by exporting a function from next.config.js and setting an environment variable that contains the menu structure. I abstracted the menu loading code into it's own file. After seeing the result, I understand better why I was not able to find an example of anyone doing something similar:
The menu is not ordered the way I would like. I could sort it alphabetically, or by the modification date but realistically it almost always needs to be manually sorted in relation to the subject of the pages. I could use an integer, either tacked on to the filename or somewhere in the file (perhaps in a comment line). But in retrospect I think that just hard coding the links in a component is probably the best way after all since it offers much more flexibility and probably isn't going to be much more work even in the very long run.
That being said I am sharing my solution as it is a way to initialize an app wide static state. It's not ideal, you will have to restart the dev server if you wish to recalculate the variables here, which is why I'm still interested in other possible solutions, but it does work. So here it is:
next.config.js
const menu = require("./libraries/menu.js");
module.exports = (phase, { defaultConfig }) => {
return {
// ...
env: {
// ...
menu: menu.get('pages'),
// ...
},
// ...
};
};
libraries/menu.js
const fs = require("fs");
const path = require("path");
const ccase = require("change-case");
module.exports = {
get: (pagePath) => {
if (pagePath.slice(-1) != "/") pagePath += "/";
let files = fs.readdirSync(pagePath);
files = files.filter((file) => {
if (file == "_app.js") return false;
const stat = fs.lstatSync(pagePath + file);
return stat.isFile();
});
return files.map((file) => {
if (file == "index.js") {
return {
name: "Home";
link: "/";
};
} else {
link = path.parse(file).name;
return {
link: link;
name: ccase.capitalCase(link);
};
}
});
},
};
Then the actual menu is generated from the environment variable in a component that can be included in the layout:
components/nav.js
import Link from "next/link";
export default class Nav extends React.Component {
render() {
return (
<nav>
{process.env.menu.map((item) => (
<Link key={item.link} href={item.link}>
<a href={item.link}>
{item.name}
</a>
</Link>
))}
</nav>
);
}
}
You can try this:
const fg = require('fast-glob');
const pages = await fg(['pages/**/*.js'], { dot: true });
I am trying to add Application Insights in my ReactJS Application. I changed the JS code that is provided on the GitHub Demo to TypeScript.. now I have
class TelemetryProvider extends Component<any, any> {
state = {
initialized: false
};
componentDidMount() {
const { history } = this.props;
const { initialized } = this.state;
const AppInsightsInstrumentationKey = this.props.instrumentationKey;
if (!Boolean(initialized) && Boolean(AppInsightsInstrumentationKey) && Boolean(history)) {
ai.initialize(AppInsightsInstrumentationKey, history);
this.setState({ initialized: true });
}
this.props.after();
}
render() {
const { children } = this.props;
return (
<Fragment>
{children}
</Fragment>
);
}
}
export default withRouter(withAITracking(ai.reactPlugin, TelemetryProvider));
But when I try to import the same component <TelemetryProvider instrumentationKey="INSTRUMENTATION_KEY" after={() => { appInsights = getAppInsights() }}></Telemetry> I get an error Severity Code Description Project File Line Suppression State
(TS) JSX element type 'TelemetryProvider' does not have any construct or call signatures.
I attempted to simply // #ts-ignore, that did not work. How do I go about solving this?
Given the example above, I hit the same issue. I added the following:
let appInsights:any = getAppInsights();
<TelemetryProvider instrumentationKey={yourkeyher} after={() => { appInsights = getAppInsights() }}>after={() => { appInsights = getAppInsights() }}>
Which seem to solve the issue for me, I am now seeing results in Application Insights as expected.
I guess if you want to have the triggers etc on a different Page/Component you may wish to wrap it in your own useHook or just add something like this to the component.
let appInsights:any;
useEffect(() => {
appInsights = getAppInsights();
}, [getAppInsights])
function trackEvent() {
appInsights.trackEvent({ name: 'React - Home Page some event' });
}
Not the best answer, but it's moved me forward. Would be nice to see a simple hooks version in typescript.
Really hope it helps someone or if they have a cleaner answer.
I've been playing around with react-responsive to understand how to make websites mobile-friendly. Essentially, what I want to do is pass in a value from a function that tests whether a viewing screen is sized like a mobile phone. Practically stolen from the documentation of react-responsive, I have a function in a file called Mobile.js as follows:
const Mobile = () => {
const mobile = useMediaQuery({ query: '(max-width: 1000px)' })
return (
<div>
{mobile && <p>You are sized like a mobile phone</p>}
</div>
);
}
However, what I want to be able to do is pass the boolean "mobile" into other classes in other js files where I can then use different CSS classNames depending on the value of this boolean.
I have 3 specific questions.
How would I return the boolean mobile from the Mobile function to be used?
How would I access this returned boolean?
How would I change the className of a div depending on the value of this boolean?
Pretty new to web development (especially React) and these questions seem super simple and easy to solve, but for some reason, I just can't seem to figure it out by my own online research. Would love direct help and also some resources where I could learn more. Thank you so much!
To me, in a perfect world, the right code would look like this in my mind. Not sure how far off I am, but I was hoping maybe this could be of some guidance as to how I'm thinking.
In the function file,
// FILE Mobile.js
const Mobile = () => {
const mobile = useMediaQuery({ query: '(max-width: 1000px)' })
return (
{ mobile } // how to return a value?
);
}
export default Mobile;
In another file,
// FILE OtherClass.js
import Mobile from './Mobile';
class OtherClass extends Component {
constructor(props) {
super(props);
this.state = { mobile: <Mobile /> } // how to access the returned value?
}
render() {
return (
<div>
{this.state.mobile && <div className="text-mobile">} // how to change the className depending on value?
{!this.state.mobile && <div className="text-desktop">}
blah blah blah blah blah
</div>
</div>
);
}
}
Thanks for asking and welcome to React development !
Here how I can help you
How would I return the boolean mobile from the Mobile function to be
used?
How would I access this returned boolean?
Since you are calling a hook useMediaQuery, you need also a hook to reuse it and returns its value :
function useIsMobile() {
const isMobile = useMediaQuery({ query: '(max-width: 1000px)' });
return isMobile
}
//Then you can share this logic in other components
function Component1(){
const isMobile = useIsMobile()
...
}
function Component2(){
const isMobile = useIsMobile()
...
}
Please note that you can't use hooks inside class components.
How would I change the className of a div depending on the value of
this boolean?
This is straightforward:
function Component(){
const isMobile = useIsMobile()
const className = isMobile ? 'mobile-class' : 'desktop-class'
return <div className={className}>...</div>
}
If you need more complex className logic you can checkout the package classnames which makes it very easy to activate/deactivate classes.
I might be misunderstanding but I think if I were to implement it according to the 3 questions you have it would be something like that:
const MyComponent = () => {
// the point of using hooks is to use it whenever you need it (you "hook it")
const isMobile = useMediaQuery({ query: '(max-width: 1000px)' });
const textStyle = isMobile ? 'text-mobile' : 'text-mobile';
return (
<div className={textStyle}>
enter code here
</div>
)
}
Hope this helps :)
Edit
To reuse this logic you can do a wrapper div component instead:
const MyWrapperComponent = (props) => {
const isMobile = useMediaQuery({ query: '(max-width: 1000px)' });
const textStyle = isMobile ? 'text-mobile' : 'text-mobile';
return (
<div className={textStyle}>
{props.children}
</div>
)
}
// Then you can use it like so:
const HomePage = () => (
<MyWrapperComponent>
write the rest of the code here
</MyWrapperComponent>
)
Children are a kind of props that are native to react, quite basically just means they are whatever you provide between the tags of the component that's receiving them :)
Hope this helps!