I am trying to get GA to track all my pages that are being changed by React Router v4.
I seen this code using the library: react-ga
history.listen(location => {
// ReactGA.set({ page: location.pathname })
// ReactGA.pageview(location.pathname)
})
but I don't think this will log the first page.
I seen this but I don't know how to set it up on my site
https://github.com/react-ga/react-ga/wiki/React-Router-v4-withTracker
My index.js looks like this
ReactDOM.render(
<Provider routingStore={routingStore} domainStores={DomainStores} uiStores={uiStores}>
<Router history={history}>
<ErrorBoundary FallbackComponent={ErrorFallbackComponent}>
<StripeProvider apiKey={stripeKey}>
<AppContainer />
</StripeProvider>
</ErrorBoundary>
</Router>
</Provider>
,
document.getElementById('app')
);
then inside "appConainter" I have "Switch" with my routes in it
<Switch>
<Route
exact
path="n"
component={}
/>
</Switch>
You need to include it in your AppContainer component:
import withTracker from './withTracker' which is a file that you manually create.
Then make the file called withTracker.jsx and put these contents in there:
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;
Make sure you have react-ga installed.
Then everywhere you have a route defined in AppContainer you need to call it:
<Route component={withTracker(App, { /* additional attributes */ } )} />
The documentation you linked shows how to do it.
Related
I tried to create useRoutes hook from react-router-dom#6.4.1 to create my App routes using JS, like this in my routes.jsx:
import { useRoutes, } from "react-router-dom";
import TestPage from "../Pages/Public/TestPage";
import TestPageTwo from "../Pages/Public/TestPageTwo";
const redirectRoutes = [
{
path: "/no-match",
element: () => <div>No Match found</div>,
},
];
const Router = () => {
const router = useRoutes([
{
path: "/testPage",
element: <TestPage />,
loader: false,
},
{
path: "/testPageTwo",
element: <TestPageTwo />,
loader: false,
},
...redirectRoutes,
]);
return <BrowserRouter>{router}</BrowserRouter>;
};
export default Router;
and in my app.jsx, I imported the file like so:
import { Provider } from "react-redux";
import Router from "./Router";
import initializeStore from "./Reducer/initializeStore";
import "./App.css";
export const store = initializeStore();
const App = () => {
return (
<Provider store={store}>
<Router />
</Provider>
);
};
export default App;
This resulted in the following error:
Uncaught Error: useRoutes() may be used only in the context of a component.
As you can see, I have wrapped the routes using BrowserRouter in the app.jsx, but I still get this error.
However, I was able to solve this when I moved the BrowserRouter to app.jsx instead of routes.jsx, like so:
app.jsx:
...
const App = () => {
return (
<Provider store={store}>
<BrowserRouter>
<Router />
</BrowserRouter>
</Provider>
);
};
...
and, in routes.jsx, I just return the router which is created using useRoutes();
I don't understand why do we need to wrap the BrowserRouter from react-router-dom#6 in the app.jsx like this instead of routes.jsx.
In react-router and react-router-dom the router components are the component that provides a routing context for all RRD hooks and components to access. It's a React context and needs to be provided higher in the ReactTree than any of the components that are consuming it.
Doesn't work:
const Router = () => {
const router = useRoutes([ // <-- accessed here outside context, error!!
....
]);
return <BrowserRouter>{router}</BrowserRouter>; // <-- provided here
};
...
const App = () => {
return (
<Provider store={store}>
<Router />
</Provider>
);
};
Works:
const Router = () => {
const router = useRoutes([ // <-- accessed here inside context, no issue :)
....
]);
return router;
};
...
const App = () => {
return (
<Provider store={store}>
<BrowserRouter> // <-- provided here
<Router />
</BrowserRouter>
</Provider>
);
};
See React Context API for more details.
In traditional React, a common pattern is to define the Routers at the entry point, and pass whatever props you need to whichever component needs them, since they're all defined
Eg,
<Switch>
<Route exact path="/">
<Home prop1={prop1}/>
</Route>
<Route path="/about">
<About prop1={prop1} prop2={prop2}/>
</Route>
<Route path="/dashboard">
<Dashboard />
</Route>
</Switch>
It's not clear to me how to do this in NextJS. The entry point _app.js has a generic that's used for all components. What would be the best way to pass prop1 to Home and About, but not Dashboard?
To be clear, these are client-side props, not server-side or static props
You can pass page specific props in getServerSideProps like below
import { GetServerSideProps } from "next";
const PageA = () => {
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return {
props: {
forbidden: true
}
}
}
export default PageA;
Then you can control that prop value in _app.js file and take action
const App = ({ Component, pageProps }) => {
if (pageProps.forbidden) {
return <Page403 />;
}
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
So, think reversely.
UPDATE
Okay, so you want _app.js to be your starting point. Here's a way to do so.
_app.js
const App = ({ Component, pageProps }) => {
if (pageProps.forbidden) {
return <Page403 />;
}
return (
<Provider store={store}>
{pageProps.forbidden ? <Component {...pageProps} /> : <Component {...pageProps} testProp={true} />}
</Provider>
)
}
In this technic, we still need to mark the pages we want that specific prop to be existed. For instance, we want that prop to be existed in pages which are not forbidden. Page A, in this case, should not get that prop.
import { GetServerSideProps } from "next";
const PageA = (props) => {
console.log('PageA props', props);//we should not see testProp here
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return {
props: {
forbidden: true
}
}
}
export default PageA;
But Page B should get it.
import { GetServerSideProps } from "next";
const PageB = (props) => {
console.log('PageB props', props);//we should see testProp here
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
return {
props: {}
}
}
export default PageB;
You can modify the logic according to your needs.
You can use getStaticProps, see the code below:
export async function getStaticProps(context) {
return {
props: {}, // will be passed to the page component as props
// export this function from each page you want to pass props, in your
// case have this function on About, Home and Dashboard pages.
}
}
for more check this link: getStaticProps
I know this question has been asked a lot, but I read every question and answer and my problem is not gone.
I'm trying to access http://localhost:3000/1 and the useParams should receive 1 to fetch data from an API, but I'm receiving undefined.
In Component.tsx I'm using console.log to receive the useParams but I'm getting undefined. All related files are posted as I'm using BrowserRouter correctly and other related React Router imports.
What is wrong in my code? Why am I not getting the correct params?
Component.tsx:
import { createContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { CharacterSchema, PageSchema } from "../interfaces/characterInterfaces";
import { IGetAllCharacters } from "../interfaces/contextInterfaces";
import { IChildren } from "../interfaces/reactInterfaces";
import api from "../services/api";
export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);
export const GetAllCharactersInfo = ({ children }: IChildren) => {
const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);
let navigate = useNavigate();
const { page } = useParams();
console.log(page);
useEffect(() => {
api
.get(`/characters?page=${page}`)
.then((res) => {
setCharactersList(res.data.data);
window.localStorage.setItem("lastPage", String(page));
return res;
})
.catch((err) => console.error(err));
}, [page]);
const nextPage = () => {
navigate(`/2`);
};
const prevPage = () => {
navigate(`/1`);
};
return (
<GetAllCharactersContext.Provider value={{ charactersList, nextPage, prevPage }}>
{children}
</GetAllCharactersContext.Provider>
);
};
AllRoutes.tsx:
const AllRoutes = () => {
return (
<Routes>
<Route path="/" element={<Navigate to="/1" />} />
<Route path="/:page" element={<Home />} />
<Route path="*" element={<Home />} />
</Routes>
);
};
export default AllRoutes;
index.tsx:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import Providers from "./contexts/Providers";
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
</React.StrictMode>
);
Having this <Route path="/:page" element={<Home />} /> will let you consume page with useParams only in Home, the component rendered by Route for this specific url.
A way to accomplish what you want is to move the fetching part inside Home. To do so export as part of the context a function that fetches and updates the state:
// ⚠️ import what's needed
export const GetAllCharactersContext = createContext<IGetAllCharacters>({} as IGetAllCharacters);
export const GetAllCharactersInfo = ({ children }: IChildren) => {
const [charactersList, setCharactersList] = useState<CharacterSchema[]>([]);
const navigate = useNavigate();
const fetchCharactersList = useCallback((page) => {
api
.get(`/characters?page=${page}`)
.then((res) => {
setCharactersList(res.data.data);
window.localStorage.setItem("lastPage", String(page));
})
.catch((err) => console.error(err));
}, []);
const nextPage = () => {
navigate(`/2`);
};
const prevPage = () => {
navigate(`/1`);
};
return (
<GetAllCharactersContext.Provider value={{ charactersList, fetchCharactersList, nextPage, prevPage }}>
{children}
</GetAllCharactersContext.Provider>
);
};
And move that useEffect you had in the provider inside Home. Something like this:
// ⚠️ import what's needed
export default function Home() {
const { fetchCharactersList, charactersList } = useContext(GetAllCharactersInfo);
const { page } = useParams();
useEffect(() => {
if (!page) return;
fetchCharactersList(page);
}, [page]);
// render data
return <div></div>;
}
Just tested this myself. useParams needs to be used inside the <Route> where the param is specified. It doesn't just pull them from the url. If you want to keep the structure the same I would suggest exposing a page setter from your context and setting it inside one of the components that can access the hook.
Alternatively you could use useSearchParams which actually takes data from the url search params itself, but you wouldn't be able to have it on the url then.
I need some help to solve the following issue with using React.
In some web app I have a landing page, where I want to redirect the user to the login page in case she or he is not logged in.
I want to use the following landing page (taken from some tutorial I found on the net) in order to use it as a model for mine.
The problem is that this is a function component while my landing page is a class component. According to what I understand I guess I need to consider the code inside useEffect and (somewhat) transfer it to componentDidMount() in my class component. But I don't know how to do that. history.replace will not work in a class component (no Hooks in Classes). Any advice from a more React experienced user will be very welcome.
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useHistory } from "react-router";
import "./Dashboard.css";
import { auth, db, logout } from "./firebase";
....
function Dashboard() {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const history = useHistory();
....
useEffect(() => { // Important part for my question !
if (loading) return;
if (!user) return history.replace("/");
....
}, [user, loading]);
return (
<div>
{/*...*/}
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
);
}
export default Dashboard;
Here is what I tried on my Class Component:
class MyCompo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(MyCompo)--");
const { history } = this.props
history.push("/login");
}
.....
}
But I get the following error:
TypeError: history is undefined
componentDidMount
=============== Added information ===============
Below is the relevant part of the code I have been working on:
This part is what works:
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
What I tried in the Links Component did not work.
The code:
....
ReactDOM.render(
<Router>
<Switch>
<Route exact path="/" component={TopMenu}>
{true && <Redirect to="/login" />}
</Route>
<Route exact path="/login" component={Login} />
<Route exact path="/section1" component={Section1Page}/>
<Route exact path="/section2" component={Section2Page}/>
<Route exact path="/section3" component={Section3Page}/>
</Switch>
</Router>,
document.getElementById('root')
);
....
const TopMenu = () => {
return (
<div className='page_container'>
<Title/>
<Links path='/'/>
<button className="dashboard__btn" onClick={logout}>
Logout
</button>
</div>
)
};
class Links extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("--componentDidMount(Links)--");
// This is some code I tried with no success.
const { history } = this.props
//history.push("/login");
}
componentDidUpdate(prevProps, prevState) {
console.log("--componentDidUpdate(Links)--");
}
render() {
return (
<div className='links_container'>
{(this.props.path != '/mng') &&
<React.StrictMode>
<Link to='/mng'>{mnMgrStr()}</Link><br/>
</React.StrictMode>}
{(this.props.path != '/other') &&
<React.StrictMode>
<Link to='/other'>{otherInpStr()}</Link><br/>
</React.StrictMode>}
.......
</div>
)
}
}
Following the example on the React Router docs you can use withRouter if your component isn't already receiving the route props, otherwise you can access history from the props.
class MyComponent extends React.Component {
...
componentDidMount() {
const { history } = this.props
// do whatever with history here
}
...
}
In react-router-dom version 5 there are a couple ways a class component can access the history object.
Rendered directly by a Route component via the component, or render or children function props so route props (i.e. history, location, and match) are passed.
component: <Route path="....." component={MyCompo} />
render: <Route path="....." render={routeProps => <MyCompo {...routeProps} />} />
Access the history object from the passed route props:
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
Decorated by the withRouter Higher Order Component so the route props are injected.
import { withRouter } from 'react-router-dom';
class MyCompo extends React.Component {
componentDidMount() {
const { history } = this.props;
history.push("/login");
}
...
}
export default withRouter(MyCompo);
Well I hope by answering this question I can save lot of time of others. Don't need to panic it's not a major issue. I will explain step by step reason and solution.
First of all why this happening is
In react-router-dom **V6 (version 6) latest ** there is no history export or redirect.
There is navigate construct.
So to achieve in functional component there is useNavigate() hook.
Now coming to answer...
To redirect in class component using react-router-dom V6 we have to use component.
So now one has to follow the following steps:
Import navigate
import { Navigate } from "react-router-dom";
Use Navigate to redirect
So above I discussed syntax to do so now coming to your exact problem
You have to redirect user to login if he is not logged in
You can follow these steps:
create state to store status of user like logged in or not (inside constructor of class)
this.state = {
userLogged: false,
};
in your render method you have to add some condition like if user is not logged in take user to login page. see below..
render() {
const { userLogged } = this.state;
if (goToPay) {
return (
<Navigate to="/cart" state={selectedTiffin} props={selectedTiffin} />
);
}
}
That's it.
It can be confusing so I am giving full example so you can save your lot of time..
import React from "react";
import { Navigate } from "react-router-dom";
class Solve extends React.Component {
constructor(props) {
super(props);
this.state = {
userLogged: false,
};
}
// here you can write code to set the status of user like logged in or not
render() {
const { userLogged } = this.state;
if (userLogged ) {
return (
<Navigate to="/cart" />
);
}
return (
<>
Here you can return your original component that should be render when user is log in
</>
);
}
}
I hope this will help and work. Thank You
I want 2 pages in my Chrome extension. For example: first(default) page with list of users and second with actions for this user.
I want to display second page by clicking on user(ClickableListItem in my case). I use React and React Router. Here the component in which I have:
class Resents extends React.Component {
constructor(props) {
super(props);
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick() {
console.log('navigate to next page');
const path = '/description-view';
browserHistory.push(path);
}
render() {
const ClickableListItem = clickableEnhance(ListItem);
return (
<div>
<List>
<ClickableListItem
primaryText="Donald Trump"
leftAvatar={<Avatar src="img/some-guy.jpg" />}
rightIcon={<ImageNavigateNext />}
onClick={this.handleOnClick}
/>
// some code missed for simplicity
</List>
</div>
);
}
}
I also tried to wrap ClickableListItem into Link component(from react-router) but it does nothing.
Maybe the thing is that Chrome Extensions haven`t their browserHistory... But I don`t see any errors in console...
What can I do for routing with React?
I know this post is old. Nevertheless, I'll leave my answer here just in case somebody still looking for it and want a quick answer to fix their existing router.
In my case, I get away with just switching from BrowserRouter to MemoryRouter. It works like charm without a need of additional memory package!
import { MemoryRouter as Router } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Router>
<OptionsComponent />
</Router>
</React.StrictMode>,
document.querySelector('#root')
);
You can try other methods, that suits for you in the ReactRouter Documentation
While you wouldn't want to use the browser (or hash) history for your extension, you could use a memory history. A memory history replicates the browser history, but maintains its own history stack.
import { createMemoryHistory } from 'history'
const history = createMemoryHistory()
For an extension with only two pages, using React Router is overkill. It would be simpler to maintain a value in state describing which "page" to render and use a switch or if/else statements to only render the correct page component.
render() {
let page = null
switch (this.state.page) {
case 'home':
page = <Home />
break
case 'user':
page = <User />
break
}
return page
}
I solved this problem by using single routes instead of nested. The problem was in another place...
Also, I created an issue: https://github.com/ReactTraining/react-router/issues/4309
This is a very lightweight solution I just found. I just tried it - simple and performant: react-chrome-extension-router
I just had to use createMemoryHistory instead of createBrowserHistory:
import React from "react";
import ReactDOM from "react-dom";
import { Router, Switch, Route, Link } from "react-router-dom";
import { createMemoryHistory } from "history";
import Page1 from "./Page1";
import Page2 from "./Page2";
const history = createMemoryHistory();
const App: React.FC<{}> = () => {
return (
<Router history={history}>
<Switch>
<Route exact path="/">
<Page1 />
</Route>
<Route path="/page2">
<Page2 />
</Route>
</Switch>
</Router>
);
};
const root = document.createElement("div");
document.body.appendChild(root);
ReactDOM.render(<App />, root);
import React from "react";
import { useHistory } from "react-router-dom";
const Page1 = () => {
const history = useHistory();
return (
<button onClick={() => history.push("/page2")}>Navigate to Page 2</button>
);
};
export default Page1;
A modern lightweight option has presented itself with the package wouter.
You can create a custom hook to change route based on the hash.
see wouter docs.
import { useState, useEffect } from "react";
import { Router, Route } from "wouter";
// returns the current hash location in a normalized form
// (excluding the leading '#' symbol)
const currentLocation = () => {
return window.location.hash.replace(/^#/, "") || "/";
};
const navigate = (to) => (window.location.hash = to);
const useHashLocation = () => {
const [loc, setLoc] = useState(currentLocation());
useEffect(() => {
// this function is called whenever the hash changes
const handler = () => setLoc(currentLocation());
// subscribe to hash changes
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
return [loc, navigate];
};
const App = () => (
<Router hook={useHashLocation}>
<Route path="/about" component={About} />
...
</Router>
);