How to properly wrap a Functional Component into a React HOC - reactjs

I am building some web pages in nextjs and I need to make sure certain pages can only be accessed if the user has been authenticated as below:
import UserManager from "../managers/user_manager";
import { useRouter } from "next/router";
import LogInPage from "../pages/auth/login";
export default function EnsureAuthenticated(OriginalComponent) {
const router = useRouter();
const loggedInUser = UserManager.getLoggedInUser();
if (loggedInUser ) {
return <OriginalComponent />;
}
return router.push(LogInPage.routePath);
}
And here is my dashboard page that I need to wrap with the above HOC to enforce authentication
import { useEffect } from "react";
import { useRouter } from "next/router";
import EnsureAuthenticated from "../../components/auth_hoc";
function DashboardHomePage(props) {
const router = useRouter();
return (<div>
Home Page
</div>);
}
DashboardHomePage.routePath = "/dashboard/home";
export default EnsureAuthenticated();
Unfortunately for me I keep getting this error after compiling in NextJS
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
Please, how do I resolve this?
Thank you.

You aren't passing DashboardHomepage component to HOC .
HOC code has to changed , we cannot use hooks in HOC body because we are calling HOC as a function instead of a component. When we call a component as a function react excludes all the lifecycles from that.
There is also an issue with the usage of router in your code, as in nextjs if we use it in render it will be rendered and client side and server side.
You may have to create a component which will do routing in useEffect and use it instead of router.push(LogInPage.routePath)
import React from "react";
import UserManager from "../managers/user_manager";
import { useRouter } from "next/router";
import LogInPage from "../pages/auth/login";
export default function EnsureAuthenticated(OriginalComponent) {
return (props) => {
const router = useRouter();
const loggedInUser = UserManager.getLoggedInUser();
if (loggedInUser ) {
return <OriginalComponent />;
}
//below code can give error as router is supposed to be called only at client side, you might have to implement it using useEffect
return router.push(LogInPage.routePath);
};
}
import { useRouter } from "next/router";
import EnsureAuthenticated from "../components/test/AddCount";
function DashboardHomePage(props) {
const router = useRouter();
return <div>Home Page</div>;
}
DashboardHomePage.routePath = "/dashboard/home";
export default EnsureAuthenticated(DashboardHomePage);

Related

Invalid hook call. when try to connect hocs with redux

I am trying to create react Hoc as functional component to check authenticated users,
I don't want to use local storage so i am using redux-persist to save the token
this is what I did
import React from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
export default Component => {
const history = useHistory();
const { token } = useSelector(state => state.user);
return token ? <Component /> : history.push('/login');
};
I got this error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

react-router throwing TypeError: useContext(...) is undefined when using useHistory

On my own code I tried to use react-router's useHistory by adding it to the imports:
import {BrowserRouter as Router, Link, Route, Switch, useHistory} from "react-router-dom";
and then defining a variable with it on my App() function:
let history = useHistory();
When I do that, I get the error:
TypeError: useContext(...) is undefined
coming from react-router's hooks.js, the specific line is:
return useContext(Context).history;
The whole file looks like this:
import React from "react";
import invariant from "tiny-invariant";
import Context from "./RouterContext.js";
import matchPath from "./matchPath.js";
const useContext = React.useContext;
export function useHistory() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useHistory()"
);
}
return useContext(Context).history;
}
export function useLocation() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useLocation()"
);
}
return useContext(Context).location;
}
export function useParams() {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useParams()"
);
}
const match = useContext(Context).match;
return match ? match.params : {};
}
export function useRouteMatch(path) {
if (__DEV__) {
invariant(
typeof useContext === "function",
"You must use React >= 16.8 in order to use useRouteMatch()"
);
}
return path
? matchPath(useLocation().pathname, path)
: useContext(Context).match;
}
Some more context:
I tried accessing React.useContext on my own code and it is defined and it is a function.
Any ideas what might be going on here?
I think that you should wrap your App in index.js with the BrowserRouter (as Router) and then in your App you define the Switch and Routes. Because you cannot use useHistory or useLocation in the same file where you use BrowserRouter.
So, use BrowserRouter wrapper one level up.
That happens because you need to wrap your component in a Router element. e.g:
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
// import MyComponent
const history = createBrowserHistory();
const MyApp = ()=> (
<Router {...{ history }}>
<MyComponent />
</Router>
);
export default MyApp;
It's definately not the case for this specific issue, but maybe someone will end up with a similar issue as I did.
For me the solution was to import hooks from react-router-dom instead of react-router package.
In my case I was trying to implement custom hook in another local package and it turned out that package didn't have react-router nor react-router-dom as dependency.
Error message was exactly the same and no other error from IDE or compiler.
Took me quite a while to figure this out. So the bottom line is: double-check your dependencies.
I've run into a similar issue with useRouteMatch(). I'm not sure if the cause is the same. I receive the error Cannot read property 'match' of undefined from line useContext(Context).match; when calling useRouteMatch() in my tests.
Option 1:
One of the ways the return from useContext can be undefined is if the Context supplied to useContext doesn't include any data. For example if you remove value={{ name: "Pupeno" }} from https://codesandbox.io/s/react-hooks-usecontext-example-wv76d?file=/src/index.js:320-347 you'll see a similar error.
There could be a similar bug in react-router-dom that allows the Context to be empty when it's called from these hooks.
Option 2:
It's hard to tell without looking at your code. It could also be something like https://github.com/ReactTraining/react-router/issues/7332

How to use history.push in HOC reactjs?

I am having an axios intercept . It will catch every request . My idea is to dispatch errors commonly in the same place. So i created this intercept. If 404 error came means i will dispatch an action and redirect the page to home. But unfortunately i cant access the props.history in HOC.
Here i am sharing the code of what i have implemented:
HOC axios intercept:
import React, {useEffect} from "react";
import axios from "axios";
const checkRequests= Wrapped => {
function CheckRequests(props) {
useEffect(()=>{
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
switch (error.response.status) {
case 404 :
props.history.push('/login') // will redirect user to login page
break
default :
break
}
// Do something with response error
return Promise.reject(error);
});
})
return (
<Wrapped {...props} />
)
}
return CheckRequests
}
export default checkRequests
And wrapping this component in App.js:
import React from "react"
import CheckRequests from "./HOC/CheckRequests"
function App(props){ ... }
export default checkRequests(App)
Error:
When is console the props in HOC it comes empty
console.log(props) => {}
Please help me with that. Is there any other way to access the history.push from that HOC. For disptaching an action an action am using store.dispatch(logout()).
Wrap the withRouter HOC in the export statement like this:
export default withRouter(checkRequests);
Don't forget to import at the top of the file
import { withRouter } from "react-router";
You may use withRouter
import { withRouter } from 'react-router-dom';
export default withRouter(checkRequests);
Thanks for your answer. I added your suggestion into my code. But i got this error You should not use Route or withRouter() outside a Router . Then i found that it is outside the router so i added this
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById('root'));
on the index.js . It works fine.

useState() Breaks Gatsbyjs/React

I have imported useStats into my index page but when I use it it breaks gatsby/react and I get this error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer
(such as React DOM)
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app See fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
I tried to trouble shoot using this from the site:
// Add this in node_modules/react-dom/index.js
window.React1 = require('react');
// Add this in your component file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);
But I got back true.
Here is my code:
import React, { useState } from "react";
import { Link } from "gatsby";
// components
import Layout from "../components/Layout/Layout";
import SEO from "../components/seo";
import IndexComponent from "../components/IndexComponent/IndexComponent";
const IndexPage = () => {
const [sku] = useState();
return (
<Layout>
<SEO title="Home" />
<IndexComponent />
</Layout>
);
};
export default IndexPage;
1.) you need [sku, setSku] = useState().
2.) Where are you rendering IndexPage? Are you doing IndexPage() instead of <IndexPage />?
I think It is a terminal Issue with windows.
Seams to work fine with bash.

Testing in React Redux

I have Dashboard component like below.
import React, { Component } from 'react';
import DataTable from './DataTable';
import { connect } from 'react-redux';
class Dashboard extends Component {
render() {
return <DataTable />;
}
}
export default connect()(Dashboard);
My test is like below
App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './components/Dashboard';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Dashboard />, div);
ReactDOM.unmountComponentAtNode(div);
});
describe('Addition', () => {
it('knows that 2 and 2 make 4', () => {
expect(2 + 2).toBe(4);
});
});
I am trying to run test using this command npm test App.test.js.
I am getting below error
Invariant Violation: Could not find "store" in the context of "Connect(Dashboard)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(Dashboard) in connect options.
Your Dashboard is connected to redux, which requires a store. You have a two possibilities :
use Enzyme and redux-mock-store in order to configure a store used when you're mounting your component. This is not well maintainable and leads to a strong dependency between Component and store.
export the non-connected Dashboard (in addition to the default export connected), and mount (eventually with the required props). This is much simpler and maintainable.
export class Dashboard extends Component {
render() {
return <DataTable />;
}
}
// Test :
import { Dashboard } from './components/Dashboard';
ReactDOM.render(<Dashboard />, div);
Note: I think you simplified your connect() for the example purpose, because it does not do anything here, but if this is your real implementation you could drop the connect part and still use the default export for your test.

Resources