Как обновить контекст React из дочернего компонента?

98

У меня есть языковые настройки в контексте, как показано ниже

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?

Mshameer
источник
Вы это читали? facebook.github.io/react/docs/context.html#updating-context Возможно, это что-то более подходящее для состояния, а не контекста
azium
@azium Да ... В этом документе контекст обновляется из самого компонента или в документ добавлена ​​ссылка на блог, которая содержит контекст, переданный в качестве реквизита поставщику контекста, мне нужно обновить его из дочернего компонента
mshameer
Эээ, нет, в документе говорится, что нельзя использовать контекст, если вам нужно его обновить. «Не делай этого», если быть точным.
Повторюсь
2
@LondonRob, какой канонический ответ вы ищете? ИМО, содержание документов мне нравится. Если вы хотите установить контекст в дочернем элементе, просто создайте сеттер в компоненте поставщика и передайте его дочернему потребителю. Затем вызовите этот установщик в дочернем потребителе и установите его для любых данных внутри дочернего элемента. По-прежнему придерживается идеи React об увеличении объемов данных.
Эндрю Ли
2
@azium просто предупреждает других, читающих этот комментарий все эти годы спустя. Обновление контекста из дочернего компонента теперь поддерживается и довольно просто: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Ответы:

226

Использование крючков

Хуки были введены в 16.8.0, поэтому для следующего кода требуется минимальная версия 16.8.0 (прокрутите вниз, чтобы увидеть пример компонентов класса). Демонстрация CodeSandbox

1. Установка родительского состояния для динамического контекста

Во-первых, чтобы иметь динамический контекст, который можно передавать потребителям, я буду использовать родительское состояние. Это гарантирует, что у меня будет единый источник истины. Например, мое родительское приложение будет выглядеть так:

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

  return (
    ...
  );
};

languageХранится в состоянии. Позже мы передадим обе функции languageи функцию установки 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. Заключение потребителя в провайдера.

Теперь я визуализирую свой компонент переключателя языка в a 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 был представлен в React 16.3, который обеспечивает отличный способ создания динамического контекста. Для следующего кода требуется минимальная версия 16.3.0. Демонстрация CodeSandbox

1. Установка родительского состояния для динамического контекста

Во-первых, чтобы иметь динамический контекст, который можно передавать потребителям, я буду использовать родительское состояние. Это гарантирует, что у меня будет единый источник истины. Например, мое родительское приложение будет выглядеть так:

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

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

  ...
}

languageХранится в состоянии наряду с методом языка сеттер, который вы можете держать вне государственного дерева.

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. Заключение потребителя в провайдера.

Теперь я визуализирую свой компонент переключателя языка в a 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 верен, однако, если провайдер valueоткажется, потребитель будет использовать значения по умолчанию.
Divyanshu Maithani
1
Почему контексты ограничиваются установкой / получением одного простого значения .... Это кажется очень неэффективным. Лучшим примером было бы выделить контекст, который имеет значение по умолчанию как объект, и соответствующим образом обновить объект.
AlxVallejo
Можно использовать контексты для того, что вы упоминаете. Пример в ответе был возвращен на основе вопроса. Поэтому для краткости он содержит только одно значение.
Дивьяншу Майтани,
1
В моем случае Typescript жалуется, если у setLanguage нет параметров. setLanguage: (language: string) => {}работает для меня.
alex351
48

Приведенный выше ответ 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
Я делаю это, и моя функция set внутри моего дочернего компонента всегда та, которую мы изначально объявили при создании контекста: () => {}
Алехандро Корредор
4
Чтобы уточнить, я думаю, что в вашем примере state.setLanguage('pk')ничего не будет, так как он const state = useContext(LanguageContext)находится за пределами LanguageContextProvider. Я решил свою проблему, переместив провайдера на один уровень вверх, а затем используя useContextдля ребенка уровень ниже.
Алехандро Корредор
2
В случае , если вы не хотите , чтобы переместить контекст уровня одного поставщика до также может использовать контекстную потребителя , как это: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.
Матин Киани
3
Мне очень нравится, как вы организовываете файл LanguageContextMangement.js. На мой взгляд, это чистый способ ведения дел, и я собираюсь начать это делать сейчас. Спасибо!
lustig
2
Спасибо за признательность, это действительно воодушевляет меня продолжать такую ​​работу!
Матин Киани
2

Одно довольно простое решение - установить состояние в вашем контексте, включив метод 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>
Struensee
источник