How to fix Circular Dependencies in React Native

Cristian Gutu
By Cristian Gutu ยท

Circular dependencies are usually ignored, this can accumulate over time and produce all sort of unexpected issues.

guu labs logos
guu labs logos

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:

guu labs logos

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
guu labs logos

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.

guu labs logos

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.

guu labs logos

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.

  1. Add Madge in our project:

    yarn add madge --dev
    
  2. Create a new script in package.json to check for circular dependencies:

    "scripts": {
      "circular-deps": "madge --circular index.tsx",
    }
    
  3. 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. ๐ŸŽ‰