子コンポーネント内からReactContextを更新する方法は?


108

私は以下のような文脈で言語設定を持っています

class LanguageProvider extends Component {
  static childContextTypes = {
    langConfig: PropTypes.object,
  };

  getChildContext() {
    return { langConfig: 'en' };
  }

  render() {
    return this.props.children;
  }
}

export default LanguageProvider;

私のアプリケーションコードは次のようになります

<LanguageProvider>
  <App>
    <MyPage />
  </App>
</LanguageProvider>

私のページには言語を切り替えるコンポーネントがあります

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher このMyPage場合、以下のようにコンテキストを更新して言語を「jp」に変更する必要があります

class LanguageSwitcher extends Component {
  static contextTypes = {
    langConfig: PropTypes.object,
  };

  updateLanguage() {
    //Here I need to update the langConfig to 'jp' 
  }

  render() {
    return <button onClick={this.updateLanguage}>Change Language</button>;
  }
}

export default LanguageSwitcher;

LanguageSwitcherコンポーネント内からコンテキストを更新するにはどうすればよいですか?


これを読んだことがありますか?facebook.github.io/react/docs/context.html#updating-contextおそらく、これはコンテキストではなく状態に適したものです
azium 2016

@aziumはい..そのドキュメントでは、コンテキストがコンポーネント自体から更新されているか、コンテキストプロバイダーに小道具として渡されたコンテキストを含むブログリンクがドキュメントに追加されています。子コンポーネントから更新する必要があります
mshameer

ええと、ドキュメントは、更新する必要がある場合はコンテキストを使用しないように言っています。正確には「やらないで」。繰り返しますが、コンテキストではなく状態を使用します
azium 2016

2
@LondonRobどんな正統な答えを探していますか?IMOのドキュメントの内容は、私には問題ないように見えます。子にコンテキストを設定する場合は、プロバイダーのコンポーネントにセッターを作成し、それを子コンシューマーに渡します。次に、子コンシューマーでそのセッターを呼び出し、子内にあるデータに設定します。それでも、データを持ち上げるというReactの考えを維持しています。
アンドリュー・リー

2
@aziumは、この数年後にこのコメントを読んでいる他の人にただ注意を向けています。子コンポーネントからコンテキストを更新することになりましサポートと非常に簡単です:hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
ラスティグ

回答:


248

フックの使用

フックは16.8.0で導入されたため、次のコードには16.8.0の最小バージョンが必要です(クラスコンポーネントの例については下にスクロールしてください)。CodeSandboxデモ

1.動的コンテキストの親状態を設定する

まず、コンシューマーに渡すことができる動的なコンテキストを作成するために、親の状態を使用します。これにより、信頼できる唯一の情報源が確実に得られます。たとえば、私の親アプリは次のようになります。

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    ...
  );
};

language状態で保存されています。後でコンテキストを介して両方languageとsetter関数setLanguageを渡します。

2.コンテキストの作成

次に、次のような言語コンテキストを作成しました。

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

ここでは、language( 'en')のデフォルトとsetLanguage、コンテキストプロバイダーからコンシューマーに送信される関数を設定しています。これらはデフォルトにすぎず、親でプロバイダーコンポーネントを使用するときにそれらの値を提供しますApp

注:LanguageContextフックを使用する場合でも、クラスベースのコンポーネントを使用する場合でも、同じままです。

3.コンテキストコンシューマーの作成

言語スイッチャーに言語を設定させるには、コンテキストを介して言語セッター機能にアクセスできる必要があります。次のようになります。

const LanguageSwitcher = () => {
  const { language, setLanguage } = useContext(LanguageContext);
  return (
    <button onClick={() => setLanguage("jp")}>
      Switch Language (Current: {language})
    </button>
  );
};

ここでは、言語を「jp」に設定しているだけですが、このための言語を設定する独自のロジックがある場合があります。

4.プロバイダーで消費者を包む

次に、言語スイッチャーコンポーネントをでレンダリングし、LanguageContext.Providerコンテキストを介してより深いレベルに送信する必要のある値を渡します。これが私の親がAppどのように見えるかです:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    <LanguageContext.Provider value={value}>
      <h2>Current Language: {language}</h2>
      <p>Click button to change to jp</p>
      <div>
        {/* Can be nested */}
        <LanguageSwitcher />
      </div>
    </LanguageContext.Provider>
  );
};

これで、言語スイッチャーがクリックされるたびに、コンテキストが動的に更新されます。

CodeSandboxデモ

クラスコンポーネントの使用

最新のコンテキストAPIはReact16.3で導入され、動的なコンテキストを持つための優れた方法を提供します。次のコードには、16.3.0の最小バージョンが必要です。CodeSandboxデモ

1.動的コンテキストの親状態を設定する

まず、コンシューマーに渡すことができる動的なコンテキストを作成するために、親の状態を使用します。これにより、信頼できる唯一の情報源が確実に得られます。たとえば、私の親アプリは次のようになります。

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  ...
}

languageあなたは状態ツリーの外側に保つことが言語setterメソッドと共に状態で保存されています。

2.コンテキストの作成

次に、次のような言語コンテキストを作成しました。

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

ここでは、language( 'en')のデフォルトとsetLanguage、コンテキストプロバイダーからコンシューマーに送信される関数を設定しています。これらはデフォルトにすぎず、親でプロバイダーコンポーネントを使用するときにそれらの値を提供しますApp

3.コンテキストコンシューマーの作成

言語スイッチャーに言語を設定させるには、コンテキストを介して言語セッター機能にアクセスできる必要があります。次のようになります。

class LanguageSwitcher extends Component {
  render() {
    return (
      <LanguageContext.Consumer>
        {({ language, setLanguage }) => (
          <button onClick={() => setLanguage("jp")}>
            Switch Language (Current: {language})
          </button>
        )}
      </LanguageContext.Consumer>
    );
  }
}

ここでは、言語を「jp」に設定しているだけですが、このための言語を設定する独自のロジックがある場合があります。

4.プロバイダーで消費者を包む

次に、言語スイッチャーコンポーネントをでレンダリングし、LanguageContext.Providerコンテキストを介してより深いレベルに送信する必要のある値を渡します。これが私の親がAppどのように見えるかです:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  render() {
    return (
      <LanguageContext.Provider value={this.state}>
        <h2>Current Language: {this.state.language}</h2>
        <p>Click button to change to jp</p>
        <div>
          {/* Can be nested */}
          <LanguageSwitcher />
        </div>
      </LanguageContext.Provider>
    );
  }
}

これで、言語スイッチャーがクリックされるたびに、コンテキストが動的に更新されます。

CodeSandboxデモ


コンテキストを初期化するデフォルト値の目的は何ですか?それらのデフォルトは常にProvider?によって上書きされませんか?
ecoe

@ecoeは正しいですが、プロバイダーがnoを渡した場合value、コンシューマーはデフォルトを使用します。
DivyanshuMaithani19年1

1
コンテキストが1つの単純な値の設定/取得に制限されているのはなぜですか...それは非常に非効率的なようです。より良い例は、デフォルトがオブジェクトであるコンテキストを強調表示し、それに応じてオブジェクトを更新することです。
AlxVallejo

1
私の場合、setLanguageにパラメーターがない場合、Typescriptは文句を言います。 setLanguage: (language: string) => {}私のために働きます。
alex3 5120

1
これありがとう。これは、私が髪を抜いたくなかった最も明確なハウツーでした。
c0dezer0 1920年

49

Maithaniによる上記の回答は素晴らしい解決策ですが、それはフックを使用しないクラスコンポーネントです。Reactでは機能コンポーネントとフックを使用することが推奨されているため、useContextフックとuseStateフックを使用して実装します。子コンポーネント内からコンテキストを更新する方法は次のとおりです。

LanguageContextMangement.js

import React, { useState } from 'react'

export const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
})

export const LanguageContextProvider = (props) => {

  const setLanguage = (language) => {
    setState({...state, language: language})
  }

  const initState = {
    language: "en",
    setLanguage: setLanguage
  } 

  const [state, setState] = useState(initState)

  return (
    <LanguageContext.Provider value={state}>
      {props.children}
    </LanguageContext.Provider>
  )
}

App.js

import React, { useContext } from 'react'
import { LanguageContextProvider, LanguageContext } from './LanguageContextManagement'

function App() {

  const state = useContext(LanguageContext)

  return (
    <LanguageContextProvider>
      <button onClick={() => state.setLanguage('pk')}>
        Current Language is: {state.language}
      </button>
    </LanguageContextProvider>
  )
}

export default App

1
私は私の子コンポーネントの内側にこれと私のセットの機能をやっていることは、常にコンテキストを作成するときに、我々は最初に宣言したものです:() => {}
アレハンドロCorredor

5
明確にするために、あなたの例でstate.setLanguage('pk')は、のconst state = useContext(LanguageContext)外にあるので、何もしないと思いますLanguageContextProvider。プロバイダーを1レベル上に移動してuseContextから、1レベル下の子で使用することで、問題を解決しました。
AlejandroCorredor19年

2
コンテキストプロバイダーを1つ上のレベルに移動したくない場合は、次のようなコンテキストコンシューマーを使用することもできます<LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>
マティーンキアニ

3
私はあなたがLanguageContextMangement.jsファイルを整理する方法が本当に好きです。それは私の意見では物事を行うためのクリーンな方法であり、私は今それを始めるつもりです。ありがとうございました!
ラスティグ

2
感謝の気持ちを込めて、このような仕事を続けていくことが本当に励みになります!
MateenKiani19年

3

非常に簡単な解決策の1つは、次のようにプロバイダーにsetStateメソッドを含めることで、コンテキストに状態を設定することです。

return ( 
            <Context.Provider value={{
              state: this.state,
              updateLanguage: (returnVal) => {
                this.setState({
                  language: returnVal
                })
              }
            }}> 
              {this.props.children} 
            </Context.Provider>
        )

そして、コンシューマーでは、次のようにupdateLanguageを呼び出します。

// button that sets language config
<Context.Consumer>
{(context) => 
  <button onClick={context.updateLanguage({language})}> 
    Set to {language} // if you have a dynamic val for language
  </button>
<Context.Consumer>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.