Как обновить контекст 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?


Вы это читали? 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>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.