I'm trying set up Google Analytics on my react site, and have come across a few packages, but none of which has the kind of set up that I have in terms of examples. Was hoping someone could shed some light on this.
The package I'm looking at is, react-ga.
My render method on my index.js looks like this.
React.render((
<Router history={createBrowserHistory()}>
<Route path="/" component={App}>
<IndexRoute component={Home} onLeave={closeHeader}/>
<Route path="/about" component={About} onLeave={closeHeader}/>
<Route path="/gallery" component={Gallery} onLeave={closeHeader}/>
<Route path="/contact-us" component={Contact} onLeave={closeHeader}>
<Route path="/contact-us/:service" component={Contact} onLeave={closeHeader}/>
</Route>
<Route path="/privacy-policy" component={PrivacyPolicy} onLeave={closeHeader} />
<Route path="/feedback" component={Feedback} onLeave={closeHeader} />
</Route>
<Route path="*" component={NoMatch} onLeave={closeHeader}/>
</Router>), document.getElementById('root'));
Keep a reference to your history object. i.e.
import { createBrowserHistory } from 'history';
var history = createBrowserHistory();
ReactDOM.render((
<Router history={history}>
[...]
Then add a listener to record each pageview. (This assumes you've already set up the window.ga object in the usual manner.)
history.listen((location) => {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
});
The question is about react-ga but this package will soon be obsolete because it doesn't support Google Analytics 4. Below is a generic solution that works with any library or native gtag. For adding GA4 to React check out this answer: https://stackoverflow.com/a/73354959/2771889.
Since react-router v5.1.0 this can be solved a lot easier with useLocation.
usePageTracking.js
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
const usePageTracking = () => {
const location = useLocation();
useEffect(() => {
// track pageview with gtag / react-ga / react-ga4, for example:
window.gtag("event", "page_view", {
page_path: location.pathname + location.search,
});
}, [location]);
};
export default usePageTracking;
App.js
const App = () => {
usePageTracking();
return (...);
};
Keep in mind that Google Analytics does automatic page tracking, but this will not work for every use case. For example, hash and search parameter changes are not tracked. This can lead to a lot of confusion. For example, when using HashRouter or anchor links the navigation will not be tracked. To have full control over page view tracking you can disable automatic tracking. See for a detailed explanation: The Ultimate Guide to Google Analytics (UA & GA4) on React (Or Anything Else
You can see this working in cra-typescript-starter where I'm using it with GA4.
Given that google analytics is loaded and initialised with a tracking id.
Here is a solution for react-router version 4 using the <Route> component to track page views.
<Route path="/" render={({location}) => {
if (typeof window.ga === 'function') {
window.ga('set', 'page', location.pathname + location.search);
window.ga('send', 'pageview');
}
return null;
}} />
You simply render this component inside the <Router> (but not as a direct child of a <Switch>).
What happens is that whenever the location prop changes it causes a re-render of this component (not actually rendering anything) that fire a pageview.
I'm using React Router v4 and the Google Analytics Global Site Tag, which appears to be recommended at the time of writing this.
And here's my solution:
Create a component wrapped in withRouter from react-router-dom:
import React from 'react';
import { withRouter } from 'react-router-dom';
import { GA_TRACKING_ID } from '../config';
class GoogleAnalytics extends React.Component {
componentWillUpdate ({ location, history }) {
const gtag = window.gtag;
if (location.pathname === this.props.location.pathname) {
// don't log identical link clicks (nav links likely)
return;
}
if (history.action === 'PUSH' &&
typeof(gtag) === 'function') {
gtag('config', GA_TRACKING_ID, {
'page_title': document.title,
'page_location': window.location.href,
'page_path': location.pathname
});
}
}
render () {
return null;
}
}
export default withRouter(GoogleAnalytics);
Simply add the component within your router (I believe ideally after any routes that would be matched and any Switch components, because the analytics function should not be priority over your site rendering):
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import IndexPage from './IndexPage';
import NotFoundPage from './NotFoundPage';
import GoogleAnalytics from './GoogleAnalytics';
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={IndexPage} />
<Route component={NotFoundPage} />
</Switch>
<GoogleAnalytics />
</Router>
);
As stated:
withRouter will re-render its component every time the route changes with the
same props as render props
So when the route changes, the GoogleAnalytics component will update, it will receive the new location as props, and history.action will be either PUSH for a new history item or POP to signal going backwards through the history (which I think shouldn't trigger a page view, but you can adjust the if statements in componentWillUpdate as you see fit (you could even try componentDidUpdate with this.props instead, but I'm unsure which is better)).
Note if you're using the react-router-dom package from react-router-4 you can handle this like so:
import { Router, Route } from 'react-router-dom';
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
const initGA = (history) => {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'YOUR_IDENTIFIER_HERE', 'auto');
ga('send', 'pageview');
history.listen((location) => {
console.log("tracking page view: " + location.pathname);
ga('send', 'pageview', location.pathname);
});
};
initGA(history);
class App extends Component { //eslint-disable-line
render() {
return
(<Router history={history} >
<Route exact path="/x" component={x} />
<Route exact path="/y" component={y} />
</Router>)
}
}
Note that this requires you to install the history package (npm install history). This is already a dependency of react-router-dom so you're not adding any page weight here.
Also note: It is not possible to use the BrowserRouter component AND instrument your ga tracking this way. This is okay because the BrowserRouter component is just a really thin wrapper around the Router object. We recreate the BrowserRouter functionality here with <Router history={history}> where const history = createBrowserHistory();.
I would suggest using the excellent react-router-ga package that is extremely lightweight and easy to configure, especially when using the BrowserRouter wrapper.
Import the component:
import Analytics from 'react-router-ga';
Then simply add the <Analytics> within your BrowserRouter:
<BrowserRouter>
<Analytics id="UA-ANALYTICS-1">
<Switch>
<Route path="/somewhere" component={SomeComponent}/>
</Switch>
</Analytics>
</BrowserRouter>
I like how Mark Thomas Müller suggests here:
In your index.js
import ReactGA from 'react-ga'
ReactGA.initialize('YourAnalyticsID')
ReactDOM.render(<App />, document.getElementById('root'))
Where your routes are:
import React, { Component } from 'react'
import { Router, Route } from 'react-router-dom'
import createHistory from 'history/createBrowserHistory'
import ReactGA from 'react-ga'
const history = createHistory()
history.listen(location => {
ReactGA.set({ page: location.pathname })
ReactGA.pageview(location.pathname)
})
export default class AppRoutes extends Component {
componentDidMount() {
ReactGA.pageview(window.location.pathname)
}
render() {
return (
<Router history={history}>
<div>
<Route path="/your" component={Your} />
<Route path="/pages" component={Pages} />
<Route path="/here" component={Here} />
</div>
</Router>
)
}
}
Short, scalable and simple :)
Always go with the library's recommended way
In the React-GA documentation, they have added a community component recommended for using with React Router: https://github.com/react-ga/react-ga/wiki/React-Router-v4-withTracker
Implementation
import withTracker from './withTracker';
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Route component={withTracker(App, { /* additional attributes */ } )} />
</ConnectedRouter>
</Provider>,
document.getElementById('root'),
);
Code
import React, { Component, } from "react";
import GoogleAnalytics from "react-ga";
GoogleAnalytics.initialize("UA-0000000-0");
const withTracker = (WrappedComponent, options = {}) => {
const trackPage = page => {
GoogleAnalytics.set({
page,
...options,
});
GoogleAnalytics.pageview(page);
};
// eslint-disable-next-line
const HOC = class extends Component {
componentDidMount() {
// eslint-disable-next-line
const page = this.props.location.pathname + this.props.location.search;
trackPage(page);
}
componentDidUpdate(prevProps) {
const currentPage =
prevProps.location.pathname + prevProps.location.search;
const nextPage =
this.props.location.pathname + this.props.location.search;
if (currentPage !== nextPage) {
trackPage(nextPage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
return HOC;
};
export default withTracker;
here is a simplest way to track all paths with some work arounds:
npm i --save history react-ga
create a file history.js
import { createBrowserHistory } from "history"
import ReactGA from "react-ga"
ReactGA.initialize(process.env.REACT_APP_GA)
const history = createBrowserHistory()
history.listen((location) => {
ReactGA.pageview(location.pathname)
})
// workaround for initial visit
if (window.performance && (performance.navigation.type === performance.navigation.TYPE_NAVIGATE)) {
ReactGA.pageview("/")
}
export default history
and then import it to where is set your Router
import history from "./history"
...
class Route extends Component {
render() {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={HomePage} />
...
</Switch>
</Router>
)
}
export default Route
References:
Gustavo Gonzalez | medium.com
History | GitHub
First, in your index.js set onUpdate function to call ga
import ga from 'ga.js';
onUpdate() {
console.log('=====GA=====>', location.pathname);
console.log('=====GA_TRACKING_CODE=====>', GA_TRACKING_CODE);
ga("send", "pageview", location.pathname);
}
render() {
return (
<Router onUpdate={this.onUpdate.bind(this)}>...</Router>
);
}
And ga.js:
'use strict';
if(typeof window !== 'undefined' && typeof GA_TRACKING_CODE !== 'undefined') {
(function(window, document, script, url, r, tag, firstScriptTag) {
window['GoogleAnalyticsObject']=r;
window[r] = window[r] || function() {
(window[r].q = window[r].q || []).push(arguments)
};
window[r].l = 1*new Date();
tag = document.createElement(script),
firstScriptTag = document.getElementsByTagName(script)[0];
tag.async = 1;
tag.src = url;
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
})(
window,
document,
'script',
'//www.google-analytics.com/analytics.js',
'ga'
);
var ga = window.ga;
ga('create', GA_TRACKING_CODE, 'auto');
module.exports = function() {
return window.ga.apply(window.ga, arguments);
};
} else {
module.exports = function() {console.log(arguments)};
}
I suggest using the Segment analytics library and following the React quickstart guide to track page calls using the react-router library. You can allow the <Route /> component to handle when the page renders and use componentDidMount to invoke page calls. The example below shows one way you could do this:
const App = () => (
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</div>
);
export default App;
export default class Home extends Component {
componentDidMount() {
window.analytics.page('Home');
}
render() {
return (
<h1>
Home page.
</h1>
);
}
}
I’m the maintainer of https://github.com/segmentio/analytics-react. With Segment, you’ll be able to switch different destinations on-and-off by the flip of a switch if you are interested in trying multiple analytics tools (we support over 250+ destinations) without having to write any additional code. 🙂
If you use hash or browser history you can do:
import trackingHit from 'tracking';
import { Router, browserHistory } from 'react-router';
browserHistory.listen(trackingHit);
// OR
import { Router, hashHistory } from 'react-router';
hashHistory.listen(trackingHit);
where ./tracking.es6
export default function(location) {
console.log('New page hit', location.pathname);
// Do your shizzle here
}
basic react-ga implementation with your index.js
var ReactGA = require('react-ga'); // require the react-ga module
ReactGA.initialize('Your-UA-ID-HERE'); // add your UA code
function logPageView() { // add this function to your component
ReactGA.set({ page: window.location.pathname + window.location.search });
ReactGA.pageview(window.location.pathname + window.location.search);
}
React.render((
<Router history={createBrowserHistory()} onUpdate={logPageView} > // insert onUpdate props here
<Route path="/" component={App}>
<IndexRoute component={Home} onLeave={closeHeader}/>
<Route path="/about" component={About} onLeave={closeHeader}/>
<Route path="/gallery" component={Gallery} onLeave={closeHeader}/>
<Route path="/contact-us" component={Contact} onLeave={closeHeader}>
<Route path="/contact-us/:service" component={Contact} onLeave={closeHeader}/>
</Route>
<Route path="/privacy-policy" component={PrivacyPolicy} onLeave={closeHeader} />
<Route path="/feedback" component={Feedback} onLeave={closeHeader} />
</Route>
<Route path="*" component={NoMatch} onLeave={closeHeader} />
</Router>), document.getElementById('root'));
Based on #david-l-walsh and #bozdoz suggestions
I created a HOC that execute the window.ga('set','page','{currentUrl}) and window.ga('send', 'pageview'); function and is easly used directly in the router page...
this is the HOC:
import React from 'react';
import { history } from '../../store'; // or wherever you createBrowserHistory(); invokation is
function withGAHistoryTrack(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { location } = history;
const page = location.pathname + location.search;
if (typeof window.ga === 'function') {
window.ga('set', 'page', page);
window.ga('send', 'pageview');
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
export default withGAHistoryTrack;
and is used this way in the router page:
<Route
path={'yourPath'}
component={withGAHistoryTrack(yourComponent)}
exact
/>
For dynamically updating url on some event (like onClick etc), following can be used:
//Imports
import ReactGA from "react-ga";
import { createBrowserHistory } from "history";
// Add following on some event, like onClick (depends on your requirement)
const history = createBrowserHistory();
ReactGA.initialize("<Your-UA-ID-HERE>");
ReactGA.pageview(history.location.pathname);
Related
I'm facing a problem with react-router-dom. I'm trying to use history.push for navigating after an action conducted. but the problem is createBrowserHistory from history is updating the urls but components are not re-rendering. I've used every solution from https://stackoverflow.com/. But it's still not working as expected.
However I found a reason behind it. As my components are wrapped with connect function connect is preventing the re-render. And there was a solution too, wrap the connect function with withRouter. I tried it too. But it's not working.
Here is My App.js
import React, { Component } from "react";
import { Router, Route } from "react-router-dom";
import history from "../history"
import Navbar from "./Navbar";
import LogIn from "./LogIn";
import StreamCreate from "./streams/StreamCreate";
import StreamDelete from "./streams/StreamDelete";
import StreamEdit from "./streams/StreamEdit";
import StreamList from "./streams/StreamList";
import StreamShow from "./streams/StreamShow";
import Profile from "./streams/Profile";
class App extends Component {
render() {
return (
<div>
<Router history={history}>
<div>
<Navbar />
<Route path="/" exact component={StreamList} />
<Route path="/streams/new" exact component={StreamCreate} />
<Route path="/streams/delete" exact component={StreamDelete} />
<Route path="/streams/edit" exact component={StreamEdit} />
<Route path="/streams/show" exact component={StreamShow} />
<Route path="/login" exact component={LogIn} />
<Route path="/my-streams" exact component={Profile} />
</div>
</Router>
</div>
);
}
}
export default App;
Here is the history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Action Creator:
import Streams from "../API/Streams";
import history from "../history";
export const createStreams = (formData) => async (dispatch, getState) => {
const { userId } = getState().auth;
const response = await Streams.post("/streams", { ...formData, userId });
dispatch({ type: "CREATE_STREAM", payload: response.data });
history.push("/")
};
I am new in ReactJs.
I need a route like localhost:3000/directory/category/region/brandName and for the same route, I need to render a component
Sample of URL be like
localhost:3000/directory/photography/france/testA
localhost:3000/directory/Catering/germany/testB
for both above URLs, a component called name.js should render
You can make use of react-router and then configure your Routes by making use of Route params
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const App () => {
return (
<Router>
<Switch>
<Route path="/directory/:profession/:country/:value" component={Name} />
<Route path="/" component={Dashboard}/>
</Switch>
)
}
Now post this you can access the params in name component and fetchData from api or have any other logic
const Name = () => {
const { profession, country, value} = useParams();
useEffect(() => {
// Any fetch logic based on params
}, [profession, country, value]);
return (
..
)
}
You can read more about react-router usage here and also refer the docs
As far as I understand from the question, you can handle this through using "Redirect" component. Let there be a "Navigation" component where the "Router" is defined as you did
Navigation.js
import Name from './name';
import From from './from';
<Router>
<Switch>
<Route path="/from">
<From />
</Route>
<Route path="/directory/:profession/:country/:value">
<Name />
</Route>
</Switch>
</Router>
and a "From" component where paths and redirections are defined. If "redirectionPath" is not null you can return "Redirect" component in render. Thus, you can redirect to and render the Name component.
From.js
import React, {Component} from 'react';
import {
Redirect
} from "react-router-dom";
class From extends Component {
state={
redirectionPath: "/directory/photography/france/testA" // or setState anywhere you need.
}
...
render(){
if(this.state.path){
return (<Redirect to={this.state.redirectionPath} />)
}
return (
<SomeComponent/>
);
}
}
This can be one of the solutions. Hope it works for you as well.
I am trying to redirect from my context following a failed update of the state from the a cookie.
import React, { createContext, Component } from 'react';
import { withRouter } from "react-router-dom";
import Cookies from 'universal-cookie';
export const MyContext = createContext();
const cookies = new Cookies();
class MyProvider extends Component {
componentDidMount() {
this.setStateFromCookie();
}
setStateFromCookie = () => {
try {
this.setState({ data: cookies.get('my-cookie')['data'] });
} catch(error) {
console.log(error);
this.props.history.push('/');
}
return
};
render() {
return (
<MyContext.Provider value={{...this.state}}>
{this.props.children}
</MyContext.Provider>
);
}
}
export default withRouter(MyProvider);
I am using a withRouter hook to this.props.history.push('/'), becuase the context is wrapping the router
class MyApp extends Component {
render() {
return (
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Router>
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</Router>
</div>
</MyProvider>
</BrowserRouter>
);
}
}
export default MyApp;
The problem is that the redirect to the home page following the error, but the home page isn't rendering.. I still see the dashboard page.
Any idea what is going on and how to fix this
The issue is that you have a nested Router wrapping your Routes. You need to remove that and then everything will work fine
<BrowserRouter>
<MyProvider>
<div className="MyApp">
<Route exact path='/' component={Index} />
<Route exact path='/dashboard' component={Dashboard} />
</div>
</MyProvider>
</BrowserRouter>
When you use a nested Router, and try to navigate from Provider, the history used by Provider is being provided by BrowserRouter and hence it isn't able to communicate to the Routes whcih are dependent on the inner <Router> component for history.
Using a single router wrapping your components solves this issue
I try to use React Route V4 to build the route of app.
as following:
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
<Provider store= {store}>
<MuiThemeProvider>
<Router history={history}>
<div>
<LayoutContainer/>
<Route exact path="/" component={Home} />
<Route path="/records" render={() =>{
return (<DealsContainer/>)
}}/>
<Route path="/sign_in" component={SignInContainer}/>
</div>
</Router>
</MuiThemeProvider>
</Provider>
I compared the method this.props.history.push('/') and history.push("/"), I find their is different way when I put it to different lifecycle method.
//dealsContainer.js
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import history from '../_helpers/history';
componentDidMount(){
//history.push("/sign_in"); //browser url will change but no redirect
//this.props.history.push("/sign_in"); //it work
}
render(){
if(this.props.authenticate.LoggedIn == false){
//this.props.history.push("/sign_in"); // it work
//history.push("/sign_in"); // it work
}
const mapStateToProps = (state) => {
return{
deals: state.deals,
authenticate: state.authenticate,
}
};
export default withRouter(connect(mapStateToProps)(DealsContainer))
And the history.push("/") in the asyncActions function was not working.
Does it possible to let it work in asyncActions?
As on subject how can I get the route name inside the handler?
For example:
var routes = <Route handler={App} path="/">
<Route name="home" path="/home" handler={HomePage} />
<DefaultRoute handler={HomePage} />
</Route>
Router.run(routes, function(Handler, state) {
var params = state.params;
React.render(<Handler params={params}/>, document.body);
});
Now suppose I have a component like this:
class HomePage extends React.Component {
render() {
return(<div>MyComponent</div>)
}
}
how can I get the current route name? To be more specific I want to get the
name="home"
attribute from
<Route name="home" path="/home" handler={HomePage} />
Before react-router 0.13 you can use this.getRoutes() using Router.State mixin.
For react-router 0.13 you can use this too:
var currentRoutes = this.context.router.getCurrentRoutes();
var lastRoute = currentRoutes[currentRoutes.length - 1];
console.log(lastRoute.name);
For react-router v2.0.x you can use:
this.props.routes[this.props.routes.length-1]
react-router 4
v4 has removed the JS API from the code base which means the above methods won't work moving forward.
The new way is to actually pass the props directly to the component being rendered, just like you normally would with any other component in you React app.
import React from 'react';
import { Match, Link, Miss } from 'react-router';
import DocumentMeta from 'react-document-meta';
import MainLayout from './Layouts/MainLayout';
import Homepage from './containers/Homepage/Homepage';
import Game from './containers/Game/Game';
import NotFound from './containers/NotFound/NotFound';
export const routes = {
homepage: {
exactly: true,
pattern: '/',
label: 'About React Lego',
component: Homepage
},
game: {
pattern: '/game',
label: 'Star Wars Trivia',
component: Game
}
};
const passPropsToRoute = ({ route, props }) => (
<span>
<DocumentMeta title={ route.title } />
<route.component {...props} routes={route.routes}/>
</span>
);
const matchWithSubRoutes = (key, i) => {
const route = routes[key];
return (<Match { ...route } key={ i } render={(props) => passPropsToRoute({ route, props })} />);
};
export function makeRoutes() {
return (
<MainLayout>
{Object.keys(routes).map(matchWithSubRoutes)}
<Miss title={`${siteTitle} - Page Not Found`} component={ NotFound } />
</MainLayout>
);
}
To see this working in a example app, take a look at react-lego which has a react-router-4 branch
Assuming we are talking about the same react-router (and the current version is 2.8.x) you can access the route directly via this.props.route.path, which will be /home for your home page (in HomePage component).
Link to the docs.
Edit: link updated.