createContextしたファイルでProviderを使わないほうが良い - it's better not to use Provider and use createContext in same file
Tags: react
Published: 2019-11-04 14:23:12

# Summary


ReactでContext APIを使うときcreateContextを使うファイルとProviderを使うファイルが同じで、Consumerだけ別のファイルで利用した場合、createContextとProviderを同一ファイルで利用すると依存が循環してしまう。
これを気にする場合は、createContxtを使うファイルも分割したほうが良い。


When using Consumer in a separate file when using the Context API in React, it is better not to use createContext and Provider in the same file, because the dependency cycles.

![dependencies daigram in Context](https://raw.githubusercontent.com/eiel-sample-code/context-dependencies-sample/master/image.png)

 {% github eiel-sample-code/context-dependencies-sample no-readme %} 


# Detail

Providerは子コンポーネントにConsumerを持つことになるので、普通に使うとConsumerへの依存ができてしまう。ProviderとcreateContextで作成したContextが同一ファイルに定義されているとConsumerはContextをimportする必要があるため、Providerの定義されたファイルをimportしてしまい循環する。
これを回避するには、createContextするファイルは独立させ、ProviderとConsumerはそれぞれContextをimportすればよい。

bad

[Context, Provider] -> [Consumer] -> [**Context**, Provider]

good

[Consumer] -> [Context]
[Provider] -> [Context][Consumer]

型やデフォルト値やContextに渡す値を含めて依存を下図に示す。
サンプルコード内の名前に合わせています。

![dependencies daigram in Context](https://raw.githubusercontent.com/eiel-sample-code/context-dependencies-sample/master/image.png)

## Sample Code (Good)

[context.js](https://github.com/eiel-sample-code/context-dependencies-sample/blob/master/src/context.js)

```js
// @flow
import { createContext } from 'react';

export type ContextValueType = {
    isSpecial: boolean;
}

const defaultValue: ContextValueType = { isSpecial: false };
export const ContextObject = createContext<ContextValueType>(defaultValue);
```



[App.js](https://github.com/eiel-sample-code/context-dependencies-sample/blob/master/src/App.js)
```js
// @flow
import React from 'react';
import { ContextObject } from './context';
import ContextConsumer from './ContextConsumer';
import type { ContextValueType } from "./context";

const contextValue: ContextValueType = { isSpecial: true };

function App() {
  return (
      <ContextObject.Provider value={contextValue}>
        <ContextConsumer />
      </ContextObject.Provider>
  );
}

export default App;
```

[ContextConsumer.js](https://github.com/eiel-sample-code/context-dependencies-sample/blob/master/src/ContextConsumer.js)
```js
// @flow
import { useContext } from 'react';
import { ContextObject } from './context';

export default function ContextConsumer(): string {
    const { isSpecial } = useContext(ContextObject);
    return isSpecial ? 'special!!!' : 'normal.';
}
```

## Sample Code(bad)

[bad tag - GitHub](https://github.com/eiel-sample-code/context-dependencies-sample/tree/bad)

[App.js](https://github.com/eiel-sample-code/context-dependencies-sample/blob/bad/src/App.js)

```js
// @flow
import React, { createContext } from 'react';
import ContextConsumer from './ContextConsumer';

export type ContextValueType = {
    isSpecial: boolean;
}

const defaultValue: ContextValueType = { isSpecial: false };
export const ContextObject = createContext<ContextValueType>(defaultValue);

const contextValue: ContextValueType = { isSpecial: true };

function App() {
  return (
      <ContextObject.Provider value={contextValue}>
        <ContextConsumer />
      </ContextObject.Provider>
  );
}

export default App;
```

[ContextConsumer](https://github.com/eiel-sample-code/context-dependencies-sample/blob/bad/src/ContextConsumer.js)

```js
// @flow
import { useContext } from 'react';
import { ContextObject } from './App';

export default function ContextConsumer(): string {
    const { isSpecial } = useContext(ContextObject);
    return isSpecial ? 'special!!!' : 'normal.';
}
```

ESLint result


```bash
$ yarn eslint .

/Users/cw-himura/Program/context-dependecies-sample/src/App.js
  3:1  error  Dependency cycle detected  import/no-cycle

/Users/cw-himura/Program/context-dependecies-sample/src/ContextConsumer.js
  3:1  error  Dependency cycle detected  import/no-cycle

✖ 2 problems (2 errors, 0 warnings)
```