Як оновити React Context зсередини дочірнього компонента?


100

У мене є налаштування мови в контексті, як показано нижче

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, яку канонічну відповідь ти шукаєш? IMO вміст документів мені здається чудовим. Якщо ви хочете встановити контекст для дочірньої організації, просто створіть установку в компоненті постачальника та передайте її дочірньому споживачеві. Потім зателефонуйте цьому сетеру в дочірньому споживачі та встановіть його відповідно до даних, що знаходяться в дочірній організації. Досі дотримується ідеї React щодо підняття даних.
Ендрю Лі

2
@azium просто повідомляє іншим, хто читає цей коментар усі ці роки потому. Оновлення контексту з дочірнього компонента тепер підтримується і досить просто: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Відповіді:


228

За допомогою гачків

Хуки були введені в 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. Обгортання споживача у постачальника

Тепер я відтворюю компонент мого перемикача мови в 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
Я роблю це, і моя функція встановлення всередині мого дочірнього компонента - це завжди та, яку ми спочатку заявляли при створенні контексту:() => {}
Алехандро Корредор

4
Щоб пояснити, я думаю, що у вашому прикладі state.setLanguage('pk')нічого не зробиш, оскільки const state = useContext(LanguageContext)знаходиться поза межами LanguageContextProvider. Я вирішив свою проблему, перемістивши постачальника на один рівень вище, а потім використовуючи useContextна дочірньому рівні нижче.
Алехандро Корредор,

2
У разі , якщо ви не хочете , щоб перемістити контекст рівня одного постачальника до також може використовувати контекстну споживача , як це: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.
Mateen Kiani

3
Мені дуже подобається, як ви впорядковуєте файл LanguageContextMangement.js. На мою думку, це чистий спосіб робити щось, і я почну це робити зараз. Дякую!
lustig

2
Дякую за вдячність, це справді спонукає мене продовжувати робити таку роботу!
Mateen Kiani

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.