Invalid hook call - React Redux - reactjs

I'm trying to implement a static method to make my code cleaner. It has worked fine until I tried to do this:
// Auth.js
import {useDispatch} from 'react-redux';
import {signOut} from './redux_actions';
export class Auth {
static signOut = () => {
const dispatch = useDispatch();
dispatch(signOut())
}
}
// Menu.js
import {Auth} from '../../auth'
...
...
<MenuItem onClick={() => {Auth.signOut()}}><ExitToAppIcon className="icon"></ExitToAppIcon><span>log out</span></MenuItem>
However I get an error:
Invalid Hook call, 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 really know what I'm doing wrong. Probably i still don't really get the architecture. Thanks for your help!
EDIT:
according to the accepted answer, this worked
import { myStore } from './index'
export default class Auth {
static signOut = () => {
myStore.dispatch(signOut())
}
}

Here are the rules of React Hooks :
Call Hooks from React function components
Call Hooks from custom Hooks
It seems that your are calling the hook useDispatch from an external function (nor a function component neither a customHook), that's why you get this error.
You can call const dispatch = useDispatch(); dispatch(signOut()); inside your component or if you really want to keep the Auth class, you can call the dispatch function directly from the store (without using the hooks) like this :
import store from './path/to/your/store'
export class Auth {
static signOut = () => {
store.dispatch(signOut())
}
}

You are trying to use a react hook in a class. This is not possible.
You have to be in a functional component to use hooks.
If you need to use a class, you can connect your component with the connect() HOC.

You can't use useDispatch or any other hook in an event handler. Hooks can only be used in top level.
This should work:
export class Auth {
static useSignOut = () => {
const dispatch = useDispatch();
return () => dispatch(signOut())
}
}
// Menu.js
import {Auth} from '../../auth'
...
...
const signOut = Auth.useSignOut(); // now `useDispatch` is called at top level
<MenuItem onClick={signOut}><ExitToAppIcon className="icon"></ExitToAppIcon><span>log out</span></MenuItem>

Related

Store hooks in React Context API as dependency injection. Is it a good pattern?

I'm working on shared libraries for multiple projects. And I need to have a flexible way of using hooks not depending on implementation. So I'm using React Context API as a dependency injection pattern.
Simplified example:
const DIContext = React.createContext();
// multiple implementations of common hook useConfig
const useReduxConfig = () => useSelector(getConfig);
const useStaticConfig = () => ({foo: 'bar'})
const useFetchConfig = () => ... //ie fetch config for server
// this will be always static value
const DIValue={
useConfig: useReduxConfig // ca be any of implementation
}
const App = () => {
return <DiContext.Provider value={DIValue}><SharedComponent /></DiContext.Provider>
}
const SharedComponent = () => {
// shared component does not care about the implementation of hook
const {useConfig} = useContext(DiContext)
const config = useConfig();
return <div>{JSON.stringify(config)}</div>
}
What do you think, is it a good pattern ?
This question is opinion-based, here is my view.
I think DI overuses react context, see use cases for context.
I would not pass down some kind of common react custom hooks to descendants component using react context. Make things complicated.
Illustration from a testing perspective:
Yeah, the DI pattern is good for testing, you can create mock objects with the same interface to replace the real object injected. But this doesn't apply to react custom hooks, we'd better not mock custom hooks, most custom hooks will rely on React built-in hooks, such as useEffect, useState, and mocking custom hooks that use these built-in hooks will result in the incorrect lifecycle and state update functionality. When testing, you still need to pass the real custom hooks to the context value rather than create mocks for these hooks. DI doesn't make testing easier as many articles said in this case.
E.g.
index.tsx:
import React, { useContext } from 'react';
import { useEffect } from 'react';
export const DIContext = React.createContext<any>({});
const useConfig = () => ({ foo: 'bar' });
// You should NOT mock useLifecycle, we need to use the real useEffect to perform component lifecycle.
const useLifecycle = () => {
useEffect(() => {
console.log('useLifecycle mount');
}, []);
};
export const DIValue = {
useConfig,
useLifecycle,
};
export const App = () => {
return (
<DIContext.Provider value={DIValue}>
<SharedComponent />
</DIContext.Provider>
);
};
export const SharedComponent = () => {
const { useLifecycle, useConfig } = useContext(DIContext);
const config = useConfig();
console.count('SharedComponent render');
useLifecycle();
useEffect(() => {
console.log('SharedComponent mount');
}, []);
return <div>{JSON.stringify(config)}</div>;
};
index.test.tsx:
import { render } from '#testing-library/react';
import React from 'react';
import { DIContext, DIValue, SharedComponent } from '.';
describe('DI via react context', () => {
test('should pass', () => {
render(
<DIContext.Provider value={DIValue}>
<SharedComponent />
</DIContext.Provider>,
);
});
});
For every component that uses context, you need to provide the same context and the real DIValue, NOT mock DIValue. If I use import rather than context, I don't need the context provider.
Although DI reduced the code for importing custom hooks in each component, the code for the context provider was added to the test code. Of course, you can create a test util function named renderWithDIContext to DRY.
There is another scenario:
If the react custom hook is used by only one component, not a common one, the file location of such a custom hook is usually next to the component file, and the component is imported directly through import, it's quick and easy.
E.g.
SharedComponent.tsx:
import { useA } from './hooks';
import { useContext } from 'react';
const SharedComponent = () => {
const a = useA();
const { useConfig } = useContext(DiContext);
const config = useConfig();
//...
}
The useA hook is only used by one component, so there is no need to import the hook into the context provider file and then put it in the context value to pass down. And, other components don't need this hook.
The most you can do are to put some common hooks in the context value.
This scenario mixes the custom hook used by the component itself with the common hook passed down through the context.
Keep it simple. Just import and use it.
import { useA } from './hooks';
import { useConfig } from '#/common/hooks';
const SharedComponent = () => {
const a = useA();
const config = useConfig();
//...
}
Besides, putting every common hook in context value and passing them down violate interface-segregation principles. The context interface is very large, but each component only wants to know about the hooks that are of interest to them, such shrunken interfaces are also called role interfaces. Of course, you can split the large context into smaller contexts. It will increase code and the need to define these role interfaces. We also need to decide how to compose these context providers with our components. Put all context providers in the root App component(application level) or page component(page-level) or module level. This adds complexity.

Is it possible to use React Hooks in class component by using HOC(Higher Order Component)?

Can I use the functional components in class components? I am going to call a function that is extracted from a functional component in class component. But it is giving errors like the following.
Unhandled Rejection (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
So I tried to call it in the functional component but even in the functional component, I got the same error as when I call it in class component.
Functional component
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
export function App() {
useEffect(() => {
async function GetBlockId() {
const wallet = useWallet();
console.log(wallet); // =====> This is not displaying.
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
};
GetBlockId()
}, []);
return <div>
<h1>{wallet}</h1>
</div>
}
Class component
import React, { Component } from 'react'
import { GetBlockId } from './util'; // =====>> I hope to get result from here.
import { hash } from './hash'
export default class App extends Component {
constructor(props) {
super(props)
}
componentDidMount(): void {
const blockNumber: any = GetBlockId(hash);
console.log(blockNumber);
}
render() {
return (
<div>
<h1>test</h1>
</div>
)
}
}
util.tsx
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
// import { Container } from './styles';
export function GetBlockId() {
useEffect(() => {
async function GetBlockId() {
const wallet = useWallet();
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
return blockNumber;
};
GetBlockId()
}, []);
}
So finally I hope to use "use-wallet" package in the class component. Is that possible? If yes, how to use useWallet hook in the class component?
React hooks are only compatible with React function components, they can't be used in class components at all. The issue with your first attempt is that you are trying to call a React hook in a callback, which breaks one of the Rules of Hooks.
Rules of Hooks
Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function,
before any early returns. By following this rule, you ensure that
Hooks are called in the same order each time a component renders.
That’s what allows React to correctly preserve the state of Hooks
between multiple useState and useEffect calls. (If you’re curious,
we’ll explain this in depth below.)
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
By following this rule, you ensure that all stateful logic in a
component is clearly visible from its source code.
You code is calling useWallet in a callback function passed to the useEffect hook. Note that this isn't the same thing as a custom Hook calling another hook.
Move the useWallet hook call out into the function component body. This will close over the wallet value in the render scope and will be available/accessible in the useEffect hook callback. I'm assuming you still only want/need the useEffect hook to run once when the component mounts, so I'm leaving that aspect alone.
import React, { useEffect } from 'react';
import { UseWalletProvider, useWallet } from 'use-wallet';
import { providers } from 'ethers';
export function App() {
const wallet = useWallet();
useEffect(() => {
console.log(wallet);
const { ethereum, connect } = wallet;
async function GetBlockId() {
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
};
GetBlockId();
}, []);
return (
<div>
<h1>{wallet}</h1>
</div>
);
}
Update
To use the useWallet hook with a class component I suggest creating a Higher Order Component that can use it and pass the wallet value as a prop.
Example:
const withWallet = Component => props => {
const wallet = useWallet();
return <Component {...props} wallet={wallet} />;
};
Decorate the class component and access via this.props.wallet
class App extends Component {
constructor(props) {
super(props)
}
componentDidMount(): void {
const { ethereum, connect } = this.props.wallet;
...
}
render() {
return (
...
);
}
}
export default withWallet(App);
You can't call react hook inside a class component.
According to ReactJS Doc you can combine the functionality.
You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
GetBlockId Is Not a React Functional Component. There is no return method; hence it will throw an error saying that you can't use a hook in a non Functional Component. Change this function to a functional component (via returning a JSX component) and it should work.
NOTE that your getBlockId function is recursive and will fail.
According to the docs. In your class component (parent component). You will want to use the UseWalletProvider and in your functional component use the hook.
Here is an example (untested), hopefully that will get you on your way.
import React, {useState} from 'react';
import { useWallet, UseWalletProvider } from 'use-wallet';
import { ethers } from 'ethers';
import { hash } from './hash'
function App() {
const wallet = useWallet()
const blockNumber = wallet.getBlockNumber()
const [blockId, setBlockId] = useState('')
useEffect(()=>{
const getBlockId = async() => {
const { ethereum, connect } = wallet;
const ethersProvider = new ethers.providers.Web3Provider(ethereum);
return await ethersProvider.getTransaction(hash);
}
setBlockId(getBlockId());
},[]);
//Note that now blockId contains the blockId that you get.
//You can use it with child components etc.
return (
<div>
<p>{blockId}</p>
</div>
);
}
function Index() {
return (
<UseWalletProvider
chainId={1}
connectors={{
// This is how connectors get configured
portis: { dAppId: 'my-dapp-id-123-xyz' },
}}
>
<App />
</UseWalletProvider>
);
}
try the following code, you'd better not using useXXX inside an function which in a functional component,
export function App() {
const wallet = useWallet();
const getBlockId = useCallback(() => {
console.log(wallet);
const { ethereum, connect } = wallet;
const ethersProvider = new providers.Web3Provider(ethereum);
const { blockNumber } = await ethersProvider.getTransaction(hash);
console.log(blockNumber);
}, [wallet]);
useEffect(() => {
getBlockId()
}, []);
return <div>
<h1>{wallet}</h1>
</div>
}

Why I face with "... which is neither a React function component or a custom React Hook function" when I use React Hook in TypeScript

I am trying to convert a react project from javascript to TypeScript. The project is created with CRA --typescript. I am using redux-saga in it. I don't have ts compile errors but I face with a runtime error.
I have already checked this question and a few others and I think I am not violating the mentioned rules.
import { IEventDto } from "dtos/event";
import React, { Dispatch, memo, useEffect } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import { createStructuredSelector } from "reselect";
import { useInjectReducer, useInjectSaga } from "utilities";
import { IGlobalState } from "utilities/iState";
import { loadNearByEvents } from "./actions";
import reducer from "./reducer";
import saga from "./saga";
import { selectEvents } from "./selector";
interface IProps {
events: IEventDto[];
}
interface IDispatches {
loadEvents: () => void;
}
// I also tested it with normall function instead of arrow function.
// function homePage(props: IProps & IDispatches): JSX.Element {
const homePage: React.FC<IProps & IDispatches> = (props) => {
useInjectReducer({ key: "home", reducer });
useInjectSaga({ key: "home", saga });
// here is the issue
useEffect(() => {
props.loadEvents();
}, []);
return (<div>
<h2>This is the home page</h2>
{props.events.map((event) => (<div>{event.title}</div>))}
</div>);
};
const mapStateToProps = createStructuredSelector<IGlobalState, IProps>({
events: selectEvents(),
});
function mapDispatchToProps(dispatch: Dispatch<any>): IDispatches {
return {
loadEvents: () => dispatch(loadNearByEvents()),
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const HomePage = compose<React.FC>(
withConnect,
memo,
)(homePage);
export { HomePage };
The error message is:
Line 23:5: React Hook "useInjectReducer" is called in function "homePage: React.FC<IProps & IDispatches>" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
Line 24:5: React Hook "useInjectSaga" is called in function "homePage: React.FC<IProps & IDispatches>" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
Line 26:5: React Hook "useEffect" is called in function "homePage: React.FC<IProps & IDispatches>" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks
The inject methods are custom react hook functions.
Likely, because it detects that homePage can never be used as a Component - React components begin with an uppercase letter. Lowercase would lead to html elements with that name to be created and your component would be ignored by React.
Of course, you wrap it later but your linter does not take that into account. So give it a different name with an uppercase first letter.

Can I use the React Context API inside a Context API or do I have to merge them?

I am just curios about if it would be possible to use the Context API inside a Context API. Like for example I would have a Context API for an AppState and want to use that in another Context API which handles a WebSocket connection?
Inspired by Joseph's answer I am thinking about just using those both context api's in a custom hook together.
useMultipleContexts(){
const contextOne = useContext(ContextOne);
const contextTwo = useContext(ContextTwo);
/**
* Do something with both contexts
* in a custom hook that can be used
* multiple times with the same state
*/
}
This is a good scenario to use hooks instead of context.
// custom hook
function useAppState() {
//add handlers here
return appState;
}
function WebSocket() {
const appState = useAppState();
// do something (i.e reconnect) every time appState changes
useEffect(() => { /* do something */, [appState])
}
function App() {
return <WebSocket />
}
Let me explain how to use two different Contexts at the same time.
First step:
You need to create two different context
const AppContext = React.createContext(null);
const SocketContext = React.createContext(null);
Second step:
You need to implement your custom hook.
const UseSharedLogic = () => {
// your common logic
}
Then share it using the context API.
<AppContext.Provider value={state}>
<SocketContext.Provider value={UseSharedLogic}>
<App />
</DispatchContext.Provider>
</StateContext.Provider>
Third step:
You need to consume these contexts at the component that you need to use them inside it.
const state = React.useContext(AppContext);
const socket = React.useContext(SocketContext);
Here you can use both contexts together and you use one value from one context in another one.
Let's assume that socket context has a function called connect and it depends on value from the app context, you can do something like this.
socket.connect(state.anyValue);
I would create a new functional component that would wrap the components
Say you had two components written as follows.
import React from 'react';
const ContextA = React.createContext({});
export default ContextA;
import React from 'react';
const ContextB = React.createContext({});
export default ContextB;
I generally avoid the above pattern because people have to guess what you're trying to put in the context. Instead I write a functional component that provides the context as follows
import { createContext, useContext } from 'react'
import ContextA from './contexta'
import ContextB from './contextb'
// The ! is needed in order to allow undefined to be set as the initial value.
const MyContext = createContext<IMyContextInfo>(undefined!);
export default ({children}) => {
const { somethingFromA } = useContext(ContextA);
const { somethingFromB }= useContext(ContextB);
return (
<MyContext.Provider value={{ a: somethingFromA, b: somethingFromB }}>
{children}
</MyContext.Provider>
);
}

Can I replace context with hooks?

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?
React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;
updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri
Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Resources