How to fix Circular Dependencies in React Native
Circular dependencies are usually ignored, this can accumulate over time and produce all sort of unexpected issues.
The problem that occurred to us was some undefined
GraphQL fragments which were causing our app to be very unstable in production but to work just fine in debug mode.
This is how the query was sent to our backend:
Those undefined
values should have been some GraphQL fragment definitions.
This was a very alarming issue and we started to address it immediately. ๐จ
What is a circular dependency?
This happens when something from file A is used in file B and something from file B is used in file A, it can be anything, a const
, a function
, a class
or an entire module
.
How to find them?
You can easily spot them by running:
npx madge --circular index.tsx
this will generate a short report:
cristian.gutu@CG-DQVHP23 frontend % npx madge --circular index.tsx
Processed 164 files (1.4s) (53 warnings)
โ Found 2 circular dependencies!
1) app/moduleA.ts > app/moduleB.ts > app/moduleC.ts
2) app/moduleD.ts > app/moduleE.ts
You can also produce some nice visual graphs, just make sure to have graphviz
installed:
brew install graphviz
npx madge --circular --image graph.svg index.tsx
How to fix them?
In our case our main issue was having a global types.ts
file where we defined a lot of types. This was imported in a lot of files and some of those files imported our types.ts
file, creating a circular dependencies, lots of them.
We found out that if we extract all the type definitions and group them by domain will solve most of the problems.
A step further is to also add all the Redux action definitions inside there:
// promoTypes.ts
export const SET_HAPTIC_FEEDBACK_ENABLED: 'SET_HAPTIC_FEEDBACK_ENABLED' =
'SET_HAPTIC_FEEDBACK_ENABLED'
export type FeedbackState = {
hapticFeedbackEnabled: boolean
}
export type SetHapticFeedbackEnabled = {
type: typeof SET_HAPTIC_FEEDBACK_ENABLED
payload: boolean
}
export type SetHapticFeedbackEnabledArgs = {
enabled: boolean
}
For example by doing this we are removing the need to import the whole promoActions.ts
file just to use that SET_HAPTIC_FEEDBACK_ENABLED
action name.
This is a very lightweight solution because you only need to import promoTypes.ts
if you want to access all the related type / action definitions.
How to prevent this?
By adding a new script as a check step in your CI/CD pipeline you can be sure that this kind of code will never be merged in the master
branch.
-
Add Madge in our project:
yarn add madge --dev
-
Create a new script in
package.json
to check for circular dependencies:"scripts": { "circular-deps": "madge --circular index.tsx", }
-
Now you can run it manually or in a CI/CD pipeline:
yarn run circular-deps
With this approach we managed to fix 80% of our circular dependencies issues. ๐