How to set up Google Analytics for React-Router? - google-analytics

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

Apply Generec Typescript for retrieving data by using api link and interface as parameter

Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.
Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.
In other words,
UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.
Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.
I have problem to make useFetch as a independent component with react TS. How should it be solved?
Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS
Thank you!
Stackblitz:
JS
https://stackblitz.com/edit/react-mjvs38?
TS
https://stackblitz.com/edit/react-ts-7oeqen?
index.tsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch2(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
useFetchTS1.tsx
import { useState, useEffect } from 'react';
interface Client {
id: number;
avatar_url: string;
}
export default function useFetch1(url) {
const [data, setData] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
useFetchTS2.tsx
import { useState, useEffect } from 'react';
interface TheComputer {
id: number;
title: string;
}
export default function useFetch2(url) {
const [data, setData] = useState<TheComputer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
There is a design that used to be called the Service Agent pattern that may work well for you:
Use React in the standard way, with useEffect etc
Views simply get type safe data and update their model
Views know nothing about APIs and just ask the agent class
The agent class can express the API interface
A lower level fetch class can do plumbing in a shared way
For sonething to compare against, see if any of my code is useful:
View classes
API classes
In these examples:
CompaniesContainer is the view class
ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
ApiClient ensures that views use only type safe requests and responses
You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.
So I believe we can get the useFetch hook to be generic for you if we change it to the following:
import { useEffect, useState } from 'react';
export default function useFetch1<TData = any>(url) {
const [data, setData] = useState<TData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.
Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:
useFetch1<Client>('https://api.github.com/users');
and
useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');
This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.
Your updated index.tsx file would look like this:
import './style.css';
import {
Link,
Route,
BrowserRouter as Router,
Routes,
useParams,
} from 'react-router-dom';
import React, { Component } from 'react';
import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';
interface Client {
id: number;
avatar_url: string;
}
interface TheComputer {
id: number;
title: string;
}
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch1<TheComputer>(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html
I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx

How to integrate React Router in Meteor?

I am trying to integrate Routes using React Router in Meteor Project. I have followed the Meteor React documentation but somehow its not working. Have tried with "Router" instead of "BrowserRouter" but no luck. Any suggestions on this.
imports/startup/client/routes.js
import { BrowserRouter, Route, Switch } from "react-router-dom";
import App, City , NotFound from "respective-modules";
export const renderRoutes = () => {
<BrowserRouter>
<div>
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/city" component={City} />
<Route component={NotFound} />
</Switch>
</div>
</BrowserRouter>;
client/main.html
<body>
<div id="react-target"></div>
</body>
client/main.jsx
import { renderRoutes } from "/imports/startup/client/routes.js";
Meteor.startup(() => {
render(renderRoutes(), document.getElementById("react-target"));
});
But a blank page is getting appeared.
If the code you are showing is correct (i.e., copied accurate from what you are running), then you just have an extra curly bracket:
export const renderRoutes = () => {
<BrowserRouter>
needs to be either:
export const renderRoutes = () =>
<BrowserRouter>
or
export const renderRoutes = () => {
return <BrowserRouter>

Update redux state with new route params when route changes

I am currently trying to implement a universal app and am using route params throughout my whole application. As such I want to put the route params into state.
I am able to do this ok for the SSR using the below...
router.get('/posts/:id', (req, res) => {
res.locals.id = req.params.id
const store = createStore(reducers, getDefaultStateFromProps(res.locals), applyMiddleware(thunk));
const router = <Provider store={store}><StaticRouter location={req.url} context={}><App {...locals} /></StaticRouter></Provider>;
const html = renderToString(router);
const helmet = Helmet.renderStatic();
res.render('index', {
content: html,
context: JSON.stringify(store.getState()),
meta: helmet.meta,
title: helmet.title,
link: helmet.link
});
});
And from here the id is put into state using the getDefaultStateFromProps function... export function getDefaultStateFromProps({ id = ''} = {}) => ({ id })
This all works perfectly and puts the correct id into the redux state, which I can then use when hitting this route.
The problem I have is that when I change route on the client side, I'm not sure how to update the redux state for the id from the url.
In terms of my handling of routes I am using the following:
import React, {Component} from 'react';
import { Switch } from 'react-router-dom';
import Header from './header/';
import Footer from './footer';
import { renderRoutes } from 'react-router-config';
export default class App extends Component {
render() {
return (
<div>
<Header />
<Switch>
{renderRoutes(routes)}
</Switch>
<Footer />
</div>
);
}
}
export const routes = [
{
path: '/',
exact: true,
component: Home
},
{
path: '/posts/:id',
component: Post,
}
{
path: '*',
component: PageNotFound
}
];
And then use the following to hydrate...
const store = createStore(reducers, preloadedState, applyMiddleware(thunk));
const renderRouter = Component => {
ReactDOM.hydrate((
<Provider store={store}>
<Router>
<Component />
</Router>
</Provider>
), document.querySelectorAll('[data-ui-role="content"]')[0]);
};
So what I'm wondering is how when I make a route change... how can I update the redux state for the new :id from the route param?
I'm a little lost in how to approach this... any help is appreciated.
You'll need to import routes from your route definition file.
import { matchPath } from 'react-router';
import { LOCATION_CHANGE } from 'react-router-redux';
// LOCATION_CHANGE === '##router/LOCATION_CHANGE';
someReducerFunction(state, action){
switch(action.type){
case LOCATION_CHANGE:
const match = matchPath(action.payload.location.pathname, routes[1]);
const {id} = match.params;
// ...
default:
return state;
}
}
Fully working example:
https://codesandbox.io/s/elegant-chaum-7cm3m?file=/src/index.js

Why am I getting `middleware is not a function' in redux?

I am setting up a simple React/Redux app. Here is my setup:
var {createStore, applyMiddleware } = require('redux')
var thunkMiddleware = require('redux-thunk')
var store = createStore(
reducers,
applyMiddleware(thunkMiddleware)
)
class App extends React.Component {
render() {
return (
<Provider store={store}>
<HashRouter>
<div className="app">
<Route exact path='/' component={Home}/>
<Route path='/Test' component={Test}/>
</div>
</HashRouter>
</Provider>
);
}
}
Also, here are my reducers:
var {combineReducers} = require('redux')
const carlist = (state=[], action) => {
switch (action.type){
case 'LOAD':
return action.test
default:
return state
}
}
module.exports = combineReducers({carlist})
When I run my app, I get this error:
applyMiddleware.js:39 Uncaught TypeError: middleware is not a function
What am I missing?
the problem looks like it might be here:
var thunkMiddleware = require('redux-thunk')
Your middleware -- here thunkMiddleware -- is not a function, suggesting that it is not being pulled from the package. You should try:
var thunkMiddleware = require('redux-thunk').default
Or, try using ES6 module syntax (since you are already using ES6 features):
import thunkMiddleware from 'redux-thunk'
I had this error when using redux-logger
I had just upgraded my packages and change
import createLogger from 'redux-logger'
to
import { createLogger } from 'redux-logger'

React Router not displaying my components

I have read all the other questions with the same issue, but it just doesn't work for me.
index.html
<body>
<h1>Index</h1>
<div id="app"></div>
</body>
client/app.jsx
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Accounts, STATES } from 'meteor/std:accounts-ui';
import { MainLayout } from '../imports/ui/layouts/main.jsx';
import { IndexPage } from '../imports/ui/components/index.jsx';
import { NotFoundPage } from '../imports/ui/components/errors/not-found.jsx';
Meteor.startup( () => {
render(
<Router history={ browserHistory }>
<Route path="/" component={ MainLayout }>
<IndexRoute component={ IndexPage } />
<Route path="signin" component={ Accounts.ui.LoginForm } formState={ STATES.SIGN_IN } />
<Route path="signup" component={ Accounts.ui.LoginForm } formState={ STATES.SIGN_UP } />
</Route>
<Route path="*" component={ NotFoundPage } />
</Router>,
document.getElementById('app')
);
});
imports/ui/layouts/main.jsx
import { Component } from 'react';
export default class MainLayout extends Component {
render() {
return (
<div>
<h2>Main Layout</h2>
{this.props.children}
</div>
);
}
}
imports/ui/components/index.jsx
import { Component } from 'react';
export default class IndexPage extends Component {
render() {
return (
<div>Index Page</div>
);
}
}
imports/ui/components/errors/not-found.jsx
import { Component } from 'react';
export default class NotFoundPage extends Component {
render() {
return (
<div>404 - Not found!</div>
);
}
}
So, going to any URL except /signin or /signup does not show anything but what's in index.html (i.e. react does not render anything)
Moreoever, /signin does not render MainLayout at all.
I have tried looking around, re-read the docs, etc. I don't see anything wrong with my code, and there is no error whatsoever. So, why isn't it working?
(Note: I have Meteor 1.3.2.4 with all latest npm modules and packages installed yesterday.)
Well, I went walking outside (it's finally sunny and warm!) and came back. The only difference I saw between my code and the example here was the export statement... or export default to be more precise.
Changing from
import { MainLayout } from '../imports/ui/layouts/main.jsx';
to
import MainLayout from '../imports/ui/layouts/main.jsx';
was my mistake.

Resources