Reputation: 4821
I'm working on creating a yarn workspace
to share code between React and React Native (upcoming blog post once it's completely done!).
The most important part for us is sharing business logic between both platforms. In this case we are using react-query
for network requests.
We've created a "Render prop" component for that
import { useAllDevices } from "../../queries/devices";
export interface DeviceListProps {
devices: any[];
isLoading: boolean,
onItemClick?: () => void;
}
export interface DeviceItemListProps {
name: string;
onItemClick?: () => void;
}
export const DeviceListContainer = ({ render }: { render: any }) => {
const { data, isLoading } = useAllDevices();
return (
<>
{render({ devices: data?.devices, isLoading })}
</>
)
}
where useAllDevices
is something like this:
export const useAllDevices = () => useQuery('useAllDevices', async () => {
const devicesResponse = await get('/todos');
return {
devices: devicesResponse.data,
};
});
In web, it works like a charm, but I'm getting an error for mobile app. It seems like the problem is with react-query itself because once I put this:
const queryClient = new QueryClient();
const App = () => {
const isDarkMode = false;
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={THEME}>
<SafeAreaView style={backgroundStyle}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>
<Header />
<Button styleType="primary">hey</Button>
</ScrollView>
</SafeAreaView>
</ThemeProvider>
</QueryClientProvider>
);
};
I get this error
It is working properly and with no problems for React web version
my package.json
on the App module is this
{
"name": "@sharecode/app",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest --updateSnapshot",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
"react": "17.0.2",
"react-native": "0.67.3",
"react-native-gesture-handler": "^2.3.0",
"styled-components": "^5.3.3"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/runtime": "^7.12.5",
"@react-native-community/eslint-config": "^2.0.0",
"@sharecode/common": "1.0.0",
"@testing-library/jest-native": "^4.0.4",
"@testing-library/react-native": "^9.0.0",
"@types/jest": "^27.4.1",
"@types/react-native": "^0.66.15",
"@types/react-test-renderer": "^17.0.1",
"@types/styled-components-react-native": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"babel-jest": "^26.6.3",
"eslint": "^7.14.0",
"get-yarn-workspaces": "^1.0.2",
"jest": "^26.6.3",
"metro-config": "^0.56.0",
"metro-react-native-babel-preset": "^0.66.2",
"nock": "^13.2.4",
"react-test-renderer": "17.0.2",
"ts-jest": "^27.1.3",
"typescript": "^4.4.4"
},
"workspaces": {
"nohoist": [
"react-native",
"react-native/**",
"react",
"react/**",
"react-query",
"react-query/**"
]
},
"resolutions": {
"@types/react": "^17"
},
"jest": {
"preset": "react-native",
"setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
}
}
Main package
{
"name": "@sharecode/common",
"version": "1.0.0",
"main": "index.ts",
"license": "MIT",
"dependencies": {
"axios": "^0.26.0",
"react-query": "^3.34.16",
"styled-components": "^5.3.3"
},
"devDependencies": {
"@types/styled-components": "^5.1.24"
}
}
And web package (working perfectly)
{
"name": "@sharecode/web",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.4.1",
"@types/node": "^16.11.26",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.13",
"react": "17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "^5.0.0",
"typescript": "^4.6.2",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"eslint-config-react-app": "^7.0.0",
"react-app-rewired": "^2.2.1"
}
}
The error seems to be pretty straightforward but I cannot see what's going on
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
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
This error is located at:
in QueryClientProvider (at App.tsx:31)
in App (at renderApplication.js:50)
in RCTView (at View.js:32)
in View (at AppContainer.js:92)
in RCTView (at View.js:32)
in View (at AppContainer.js:119)
in AppContainer (at renderApplication.js:43)
in NuoDoor(RootComponent) (at renderApplication.js:60)
✨ Done in 318.43s.
Also, here is the result of `yarn why react``
javiermanzano@Javiers-MBP app % yarn why react
yarn why v1.22.17
[1/4] 🤔 Why do we have the module "react"...?
[2/4] 🚚 Initialising dependency graph...
[3/4] 🔍 Finding dependency...
[4/4] 🚡 Calculating file sizes...
=> Found "@sharecode/app#[email protected]"
info Reasons this module exists
- "_project_#@sharecode#app" depends on it
- in the nohoist list ["/_project_/@sharecode/app/react-native","/_project_/@sharecode/app/react-native/**","/_project_/@sharecode/app/react","/_project_/@sharecode/app/react/**","/_project_/@sharecode/app/react-query","/_project_/@sharecode/app/react-query/**"]
info Disk size without dependencies: "356KB"
info Disk size with unique dependencies: "404KB"
info Disk size with transitive dependencies: "432KB"
info Number of shared dependencies: 3
=> Found "[email protected]"
info Reasons this module exists
- "_project_#@sharecode#web" depends on it
- Hoisted from "_project_#@sharecode#web#react"
info Disk size without dependencies: "356KB"
info Disk size with unique dependencies: "404KB"
info Disk size with transitive dependencies: "432KB"
info Number of shared dependencies: 3
✨ Done in 1.17s.
I hope I explained the problem! Any help is appreciated :)
Thank you!
Upvotes: 7
Views: 2839
Reputation: 353
I don't think your problem is related to having mismatching versions of React and the renderer, so we end with 2 options.
3. You might have more than one copy of React in the same app
I'll start covering the 3rd since is the most probable, in this case your program is detecting two reacts.
In your yarn why react
you got a react
from "_project_#@sharecode#app"
and "_project_#@sharecode#web"
Hoisted from "_project_#@sharecode#web#react"
, remember this Hoisted info cuz it's important for solve your problem.
Looking at your package.json
you do have the react
installed on both. And looking at your workspaces
nohoist
in "@sharecode/app"
you say to don't hoist the react
package, but as I said before it is be hoisting!.
Well, as I understood and are illustrated at the nohoist documentation you need to put the nohoist
config at the package.json
in your root "monorepo" (In your case the @sharecode/common
).
It will look like this:
{
"name": "@sharecode/common",
...,
"devDependencies": {
...
},
"workspaces": {
"packages": ["packages/*"],
"nohoist": [
...,
"**/react",
"**/react/**",
...
]
}
}
Than, with your package.json
working as expected when you run yarn why react
you should get something like this:
...
=> Found "@sharecode/app#[email protected]"
info Reasons this module exists
- "_project_#@sharecode#app" depends on it
- in the nohoist list ["/_project_/**/react-native","/_project_/**/react-native/**","/_project_/**/react","/_project_/**/react/**","/_project_/**/react-query","/_project_/**/react-query/**"]
...
Maybe a found
in the "_project_#@sharecode#web"
too, with other nohoist
probably. I didn't checked it myself, but with this correction said above you should be with everything running fine. You can check this and this for further explanation on how it works. But I'm pretty sure that the problem is the nohoist
not being in the "root monorepo".
P.S.: I tryed to find what is the reason for using "packages": ["packages/*"],
and didn't find out. But I'm deducing that it is to say that you're hoisting everything that aren't in nohoist.
2. You might be breaking the Rules of Hooks
Well, if you couldn't solve your problem with the first option, your problem is related to a hook.
I don't know if it's this your problem, but you're calling a bool
in a const
and probably trying to change it in some place. Can't you refactor to use a useState(false)
?
Also check if you'd called some hook inside loops, conditions or nested funcitons in some of your children, probably the QueryClientProvider if I don't miss understood.
Searching for the Rules of Hooks I found this:
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.)
Upvotes: 2