DEV Community

Tomohiko Himura
Tomohiko Himura

Posted on

createContextしたファイルでProviderを使わないほうが良い - it's better not to use Provider and use createContext in same file

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

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

Sample Code (Good)

context.js

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

export type ContextValueType = {
    isSpecial: boolean;
}

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

App.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

// @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

App.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

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

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

ESLint result

$ 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)

Top comments (0)