I have a custom hook:
const useMyHook = () => {
const [state, setState] = useState();
...
return state;
};
When I use this hook within the same project it's working fine:
const MyComp = () => {
const state = useMyHook();
...
return <ChildComp {...state} />
}
But when I export this hook from an external package (that I created):
import {useMyHook} from 'my-package';
const MyComp = () => {
const state = useMyHook();
...
return <ChildComp {...state} />
}
I get TypeError: null is not an object (evaluating 'dispatcher.useState') with the following general error:
Warning: 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
I want to assume that the problem is not the custom hook since it's working initially. I tought maybe it has to do something with versioning so in the external package I define the package.json with:
"peerDependencies": {
"react": "18.1.0"
},
Same version as the react dependency in my original project in order to resolve react duplication as the error says.
Nothing of the solutions in the web works for me. Any idea what can cause this behavior?
FYI: I'm using react-native (with Expo).
Related
I have a React app created with create-react-app.
I'm trying to make a custom hook using Microsoft Authentication Library (MSAL). MSAL has a custom React hook that I want to call from my own custom hook.
When I use a hook (any hook) inside my custom hook in a separate file I get this in the browser:
Warning: 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
// ourhook/index.ts
import { useEffect } from "react";
export const useMsal2 = () => {
useEffect(() => {
console.log("Hello from our hook!");
});
};
// app.tsx
import React from "react";
import { useMsal2 } from "./ourhook";
const App = () => {
useMsal2();
return <div>App</div>;
};
export default App;
If I call
const { instance } = useMsal();
directly from App.tsx everything works fine. It only appears to be a problem if my custom hook is in its own file.
From what I see I'm not violating any hook rules. I'm calling a hook that's calling a hook, and the first call is from a top level component.
I have read other threads here about hooks in hooks, but none of them has an answer that fits this problem.
Have I missed something about hook rules, or what might be causing this?
Okay, I forgot that we tried to have /ourhook as a freestanding project and then copy pasted it into a create react app app.
Some of you were right, it did have its own version of react.
I'm just going to hide under a rock for the rest of the week.
Thanks for all your help! <3
Try to add this comment just above:
import { useMsal } from "#azure/msal-react";
export const useMsal2 = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { instance } = useMsal();
const request = "";
return {
loginRedirect: () => console.log(""),
}
};
I don't know what useMsal looks like, but from what I see, you don't actually violate any hook rule.
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
Trying out Next.js but I'm struggling with the following. Just tried to install react-hook-mousetrap and imported it like I normally would:
import useMousetrap from "react-hook-mousetrap";
This gives me the following error:
SyntaxError: Cannot use import statement outside a module
1 > module.exports = require("react-hook-mousetrap");
I am not sure what this means? I then thought it might be a problem with Nextjs's SSR, since my library enables hotkeys and will use some browser APIs. If you already know that I am on the wrong track here you can stop reading now.
What I did next however was this, I tried dynamic imports:
1. Dynamic import with next/dynamic
First thing I came across was next/dynamic, but this seems to be for JSX / React Components only (correct me if I'm wrong). Here I will be importing and using a React hook.
2. Dynamic import with await (...).default
So I tried dynamically importing it as a module, but I'm not sure how to do this exactly.
I need to use my hook at the top level of my component, can't make that Component async and now don't know what to do?
const MyComponent = () => {
// (1) TRIED THIS:
const useMousetrap = await import("react-hook-mousetrap").default;
//can't use the hook here since my Component is not async; Can't make the Component async, since this breaks stuff
// (2) TRIED THIS:
(async () => {
const useMousetrap = (await import("react-hook-mousetrap")).default;
// in this async function i can't use the hook, because it needs to be called at the top level.
})()
//....
}
Any advice here would be appreciated!
The error occurs because react-hook-mousetrap is exported as an ESM library. You can have Next.js transpile it using next-transpile-modules in your next.config.js.
const withTM = require('next-transpile-modules')(['react-hook-mousetrap']);
module.exports = withTM({ /* Your Next.js config */});
I don't know if my answer is actual, but i'm facing whith this problem today, and what that i done:
//test component for modal
const Button: React.FC<{close?: () => void}> = ({ close }) => (
<React.Fragment>
<button type="button" onClick={ close }>Close</button>
</React.Fragment>
);
// async call import react custom hook in next js whithout a dynamic
//import
let newHook;
(async () => {
const { hookFromNodeModules } =
await import('path/to/hook');
newHook = hookFromNodeModules;
})();
export default function Home() {
// check if hook is available
const openModal = newHook && newHook();
const { t } = useTranslation('common');
// useCallback for update call function when hook is available
const handleOpen = React.useCallback(() => {
openModal?.openModal(Button, {});
}, [openModal]);
return ( ...your code )
}
hope this help!)
screen from next.js app
I'm trying to make a thin wrapper to the "jsoneditor" library using a functionnal component. I'm rather new to React and worked so far mainly with hooks. So I tried to adapt the example given by the author of the library to use hooks:
https://github.com/josdejong/jsoneditor/tree/master/examples/react_demo
This is what I came up with so far:
import React, {useRef, useState, useEffect, useCallback} from 'react'
import JSONEditor from 'jsoneditor'
import styles from './JSONEditorReact.module.css'
import 'jsoneditor/dist/jsoneditor.css';
function App(){
const [json, setJson] = useState({some_key:"some_value"})
function onChangeJson(json){
setJson(json)
}
return <JSONEditorReact onChangeJson={onChangeJson} json={json}/>
}
function JSONEditorReact({onChangeJson, json}){
const containerRef = useRef()
const editorRef = useRef() // used as a namespace to have a reference to the jsoneditor object
useEffect(
() => {
console.log("mounting")
const options = {
modes: ['tree','form','view','text'],
onChangeJSON: onChangeJson
}
editorRef.current = new JSONEditor(containerRef.current, options)
return () => editorRef.current.destroy()
},
[] //eslint complains about the missing dependency "onChangeJson" here
)
useEffect(
() => {
console.log("updating")
editorRef.current.update(json)
},
[json]
)
return (
<div className={styles.container} ref={containerRef} />
)
}
export default App;
It works - but eslint complains about onChangeJson being a missing dependency in useEffect. If I add it as a dependency, useEffect runs each time the user inputs something into the json editor. This implies that the user looses focus on the editor each time he enters a character. My understanding is that when it occurs, setJson function of App is called, so App component is refreshed, causing the onChangeJson function to be-reinstanciated, so the first useEffect is rerun, and a new JSONEditor is instanciated.
I had some ideas but they don't seem satisfying:
define onChangeJson with useCallback - issue : I find it daunting to call useCallback each time I want to use my component
pass the setter function setJson of App as the "onChangeJson" property of JSONEditorReact - issue: what if I want to perform more actions than just setting the state in my callback?
Any more relevant ideas to solve the missing dependency issue without running the first useEffect on each input?
Is this a kind of use case where class components are more relevant than functional components using hooks? (the wrapper using class components looks more straightforward than mine, where I had to use a ref to create a namespace to hold my JSONEditor instance)