Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 circular dependency in react project

I've just set up a little test project using react native. It's all new to me (including ECMAScript 6). Eslint is telling me something about "circular dependencies" and i don't know how to solve this problem. The code is working nevertheless.

my package.json:

...
"dependencies": {
    "axios": "^0.19.0",
    "node-sass": "^4.12.0",
    "react": "16.8.3",
    "react-native": "0.59.9",
    "react-navigation": "^3.11.0"
},
"devDependencies": {
    "@babel/core": "7.4.5",
    "@babel/runtime": "7.4.5",
    "babel-eslint": "^10.0.1",
    "babel-jest": "24.8.0",
    "babel-plugin-module-resolver": "^3.2.0",
    "eslint": "^5.16.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-import-resolver-babel-module": "^5.1.0",
    "eslint-plugin-import": "^2.17.3",
    "eslint-plugin-jsx-a11y": "^6.2.1",
    "eslint-plugin-react": "^7.13.0",
    "jest": "24.8.0",
    "metro-react-native-babel-preset": "0.54.1",
    "react-dom": "^16.8.6",
    "react-test-renderer": "16.8.3"
},
...

src/index.jsx is the main JSX file:

import { Comp1 } from 'components';
...

I created a src/components/index.jsx to enable imports like

import { Comp1, Comp2, Comp3 } from 'components'

instead of

import { Comp1 } from 'components/comp1';
import { Comp2 } from 'components/comp2';
import { Comp3 } from 'components/comp3';

The file src/components/index.jsx looks like:

export * from './button';
export * from './comp1';
...

src/components/button/index.jsx:

import React from 'react';
import {
  Text,
  TouchableOpacity
} from 'react-native';
import style from './style';

const Button = ({ onPress, children }) => {
  const {
    buttonStyle,
    textStyle
  } = style;

  return (
    <TouchableOpacity onPress={onPress} style={buttonStyle}>
      <Text style={textStyle}>
        {children}
      </Text>
    </TouchableOpacity>
  );
};

export default Button;
export { Button };

src/components/comp1/index.jsx:

import React from 'react';
import {
  Text,
  View
} from 'react-native';
import { Button } from 'components';
import style from './style';

const Comp1 = (props) => {
  const {
    textStyle,
    viewStyle
  } = style;

  return (
    <View style={viewStyle}>
      <Text style={textStyle}>some text</Text>
      <Button>Test</Button>
    </View>
  );
};

export default Comp1;
export { Comp1 };

Running this setup produces an eslint error import/no-cycle. The code itself works.

If i change import { Button } from 'components' in src/components/comp1/index.jsx into import { Button } from 'components/button' the no eslint error pops up.

I would like to use this short import syntax like described above without loosing the possibility of using the modules inside of each other. Is there a way?

like image 673
iwanuschka Avatar asked Jun 27 '19 14:06

iwanuschka


1 Answers

Your structure is setting up a cyclic dependency between components/index.jsx and comp1/index.jsx (and others where you have this same thing). comp1/index.jsx imports from components/index.jsx, which imports from comp1/index.jsx.

The runtime handling of cycles is different between actual native ESM¹ modules and CJS² or similar. Both require careful handling of cycles, but the ways they behave differ slightly. This can be particularly confusing if you're transpiling ESM to CJS in your bundler or similar.

When there's a cyclic dependency between two modules (to keep it simple), it means at some point one of the two modules will run before the other, which means any imports it takes from the other will either be uninitialized (ESM) or not yet be defined (CJS). So top-level code in one of the two modules can't rely on the imports existing yet. In ESM, trying to use an uninitialized export raises an error; in CJS, the export's value is just undefined.

In your example, I wouldn't expect that to be a problem, because the top-level code in the modules in the cycle doesn't use the imports from the other module in the cycle, it's only used in functions called later (comp1/index.jsx uses Button, but only when Comp1 is called, and it's not called at the top-level code. (The process can be more complicated than that, but...)

If the code is tested and working, you might use configuration comments to disable that rule for the component files (if it lets you do that). This lets you leave the rule on globally in case of actually-problematic cycles, but not get bothered about these cycles you've tested and know are good.


¹ ESM = ECMAScript Modules, genuine native JavaScript modules, which are statically analyzable if you use only import and export declarations (not import() dynamic imports).

² CJS = CommonJS, a dynmic module structure using require and an exports object.

like image 57
T.J. Crowder Avatar answered Oct 01 '22 09:10

T.J. Crowder