Invalid hook call. when try to connect hocs with redux - reactjs

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

Related

I'm Receiving Invalid Hook Call error either from inside a functional component

In my project I have the index.tsx calling the App.tsx that uses a UserProvider and AuthProvider. I received an invalid call error from inside the UserProvider because I'm using the useState hook.
This problem occurs also if I create a custom hook, I can't use any hook inside the custom hook.
This is my index.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
//import './commons/global.css';
import reportWebVitals from './reportWebVitals';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
reportWebVitals();
This is my App.tsx:
import { useEffect, useState } from 'react';
import { BrowserRouter, useNavigate } from 'react-router-dom';
import {
AuthService,
AuthProvider,
useAuth,
useLogin,
UserProvider,
} from 'reactjs-oauth2-pkce-provider';
import './index.css';
import Routes from './routes';
const authService = new AuthService({...});
const App = () => {
const [user, setUser] = useState({});
const [loading, setLoading] = useState(true);
// for every refresh of the page, check if there is a user in localStorage
useEffect(() => {
const user = localStorage.getItem('user');
if (user) {
setUser(JSON.parse(user));
}
setLoading(false);
}, []);
return (
<UserProvider>
<AuthProvider authService={authService}>
<Routes />
</AuthProvider>
</UserProvider>
);
};
export default App;
This is my UserProvider.tsx:
import React, { ReactElement, useState } from 'react';
import { UserContext } from '#app/application/UserContext';
export const UserProvider = ({ children }: { children: ReactElement }) => {
const [user, setUser] = useState({});
console.log(user);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
There is no function that is calling the hook outside a functional component (there is?)!
A important thing to note is that the UserProvider.tsx is part of a separate package for authentication that I'm building and importing from it with yarn link.
I already tested to install directly from github without success.
I already tested declaring the function with React.FC type but the result is the same.
The understanding that I have from it until now is that the react do not know that the App() function is a functional component and the call for UserProvider is inside a functional component.
I'm expecting that I can use hooks inside the provider, to work useState, useEffect, useNavigate.
The error message is telling you that your error is in fact originating from your Hook call in UserProvider.tsx at line 5.
When you don't import components traditionally as react Components such as
<Component/>
and instead import them as a function such as
return component();
or as in your case, importing them as a wrapper:
<Component>Parent</Component>
Then React hooks won't work.
I would recommend using Redux for logins or the useContext Hook to manage logins if you can't figure out the Traditional React Hooks way of managing this. useContext is also a React hook which would cause the same error if implemented in a similar way, but Redux wouldn't cause you to adhere to any of these strict React rules..
There's a lot of existing Login Templates on Github, including ones with Redux. Implementing Logins is the most boring and tedious process I've dealt with, which can usually take extremely long. Traditionally I use App Skeletons with login capabilities already implemented, and then add all the fun code over, but try the suggestions I mentioned.
The error message that you posted contains three possible reasons for the error. Based on the information that you've provided, reason #3 seems like the most likely culprit.
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
I don't see anywhere in your code, at least not in what you've posted, that you are violating the rules of hooks.
It's what you've said here that jumps out:
A important thing to note is that the UserProvider.tsx is part of a separate package for authentication that I'm building and importing from it with yarn link.
I suspect that your reactjs-oauth2-pkce-provider package is declaring react in the dependencies rather than the peerDependencies. This would cause you to have two copies of react -- one from the package and another from your main app. If these two versions don't match, you could be dealing with reason #1 as well.
The "Duplicate React" section of the docs contains a few checks that you can do to confirm that you do in fact have two copies of React.
If you see more than one React, you’ll need to figure out why this happens and fix your dependency tree. For example, maybe a library you’re using incorrectly specifies react as a dependency (rather than a peer dependency).
Since this is a package that you created and control, you can fix the problem at its root. the solution is to remove react from the dependencies array in your package's package.json and move it to the peerDependencies array instead. You may also need it in the devDependencies. Here is a good example.

Using React.Context with Nextjs13 server-side components

Next13 was released a week ago, and I am trying to migrate a next12 app to a next13.
I want to use server-side components as much as possible, but I can't seem to use
import { createContext } from 'react';
in any server component.
I am getting this error:
Server Error
Error:
You're importing a component that needs createContext. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
,----
1 | import { createContext } from 'react';
: ^^^^^^^^^^^^^
`----
Maybe one of these should be marked as a client entry with "use client":
Is there an alternative here or do I have to resort to prop drilling to get server-side rendering?
It seems like I can use createServerContext
import { createServerContext } from 'react';
If you're using Typescript and React 18, you'll also need to add "types": ["react/next"] to your tsconfig.json compiler options, since this is a not-yet-stable function.
This is a new feature from React's SSR to recognize whether a component is client-side or server-side. In your case, createContext is only available on the client side.
If you only use this component for client-side, you can define 'use client'; on top of the component.
'use client';
import { createContext } from 'react';
You can check this Next.js document and this React RFC for the details
According to Next.js 13 beta documentation, you cannot use context in Server Components:
In Next.js 13, context is fully supported within Client Components, but it cannot be created or consumed directly within Server Components. This is because Server Components have no React state (since they're not interactive), and context is primarily used for rerendering interactive components deep in the tree after some React state has been updated
However, there are alternative ways to handle data in the new approach, depending on your case. F.e. if you fetched the data from the server in a parent component and then passed it down the tree through Context, you can now fetch the data directly in all the components that depend on this data. React 18 will dedupe (de-duplicate) the fetches, so there are no unnecessary requests.
There are more alternatives in the documentation.
I've made a tiny package to handle context in server components, works with latest next.js, it's called server-only-context:
https://www.npmjs.com/package/server-only-context
Usage:
import serverContext from 'server-only-context';
export const [getLocale, setLocale] = serverContext('en')
export const [getUserId, setUserId] = serverContext('')
import { setLocale, setUserId } from '#/context'
export default function UserPage({ params: { locale, userId } }) {
setLocale(locale)
setUserId(userId)
return <MyComponent/>
}
import { getLocale, getUserId } from '#/context'
export default function MyComponent() {
const locale = getLocale()
const userId = getUserId()
return (
<div>
Hello {userId}! Locale is {locale}.
</div>
)
}
This is the code for it, it's really simple:
import 'server-only'
import { cache } from 'react'
export default <T>(defaultValue: T): [() => T, (v: T) => void] => {
const getRef = cache(() => ({ current: defaultValue }))
const getValue = (): T => getRef().current
const setValue = (value: T) => {
getRef().current = value
}
return [getValue, setValue]
}

How to properly wrap a Functional Component into a React HOC

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);

Testing a react component using useSelector hook

I am having a lot of trouble trying to implement tests for a component using the useSelector hook from react redux. I've seen some questions already about this subject but I didn't manage to fix my problem using the suggested solutions to those questions.
My component is pretty big so I won't post it all but the part giving me trouble looks like this :
Total.tsx
import React from 'react';
import clsx from 'clsx';
import i18next from 'i18next';
import { useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import Box from '#material-ui/core/Box';
import CustomTooltip from '../CustomTooltip/CustomTooltip';
import SkeletonTotal from 'components/Skeletons/Total';
import { ApplicationHelper } from 'helpers';
import './Total.scss';
//Some interfaces here for types since this is in TypeScript
function Total(props: TotalProps) {
const { currency } = useSelector(
(state: { currencyReducer: any }) => state.currencyReducer
);
...
}
I first tried to test it like another component that doesn't use redux like so :
Total.test.js (first attempt)
import React from 'react';
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
);
});
But I was getting an error saying I need a react-redux context value and to wrap my component in a Provider which led me to try this :
Total.test.js (attempt 2)
import React from 'react';
import { Provider } from 'react-redux'
import Total from './Total';
import { render } from '#testing-library/react';
test('test', () => {
const { container } = render(
<Provider>
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />
</Provider>
);
});
I am now getting a "Cannot read property 'getState' of undefined" error for the Provider component. I did try to mock a store to pass to my Provider as well as using jest to mock a return value like so
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({currency: 'cad'})
Unfortunately I was unsuccessful to make this work and could not find a working solution in the other questions that might relate to this. Any ideas how I could make this work? Thanks
The useSelector hook relies on the redux Context in order to access the state, so it must be inside of a Provider component in order to work. Your second attempt is on the right track, but you haven't set the store prop on the Provider, so the store is undefined and you get error "Cannot read property 'getState' of undefined".
Since you'll likely have many components that you'll want to test with redux context, the redux docs suggest creating your own version of the react testing library's render function which wraps the element in a provider before rendering it. This new render function adds two new optional options to the standard RTL options: initialState and store.
You can basically copy and paste that entire test-utils.js example from the docs, but I modified the return to include the created store so that we can dispatch to it directly (rather than just interacting with the component in ways that will dispatch an action).
return {
...rtlRender(ui, { wrapper: Wrapper, ...renderOptions }),
store
};
With typescript annotations.
Inside your component test file, you will use your test-utils to render the Total component. It's fine to return the container element but you don't actually need to because you can query matching elements on the global RTL screen object or on the bound queries for your base element. We are basically looking to see that the outputted HTML code matches the expectations. You could test the selector itself in isolation, but it seems like you are trying to test the component.
Your test might look something like this:
import React from "react";
import Total from "./Total";
import { render, screen } from "./test-utils";
// if you want events: import userEvent from "#testing-library/user-event";
test( 'gets currency from redux', () => {
// render with an initial currency
const { store, container, getByLabelText } = render(
// not sure where these props come from, presumable constants in the file
<Total priceLoading={false} bookingPrice={bookingPrice} values={myFormValues} />,
{ initialState: { currency: USD } }
);
// some sort of RTL matcher or document.querySelector
const currencyElement = getByLabelText(/currency/i); // uses regex for case-insensitivity
// some sort of check for value
expect(currencyElement?.innerText).toBe("USD");
// dispatch an action to change the currency
// might want to wrap in `act`?
store.dispatch(setCurrency("EUR"));
// check that the value changed
expect(currencyElement?.innerText).toBe("EUR");
});
Working example that I created based on a basic counter component.

NextJS (React) - Error: Invalid hook call (useState)

I am using NextJS for server side rendering & I am trying to use useState hook inside a component which is throwing :
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
My code:
import { useState } from 'react';
const Banner = ({ movies }) => {
const [movie, setMovie] = useState(movies);
return (
<header>
<h1> Banner </h1>
</header>
)
}
export default Banner;
Directory Tree
/components/Banner.js
/pages
Getting same error when i use useEffect() like :
import React, {useEffect} from 'react'

Resources