Jak zaktualizować kontekst reakcji z wnętrza komponentu potomnego?

109

Mam ustawienia języka w kontekście jak poniżej

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

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

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

export default LanguageProvider;

Mój kod aplikacji będzie wyglądał jak poniżej

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

Moja strona zawiera komponent do zmiany języka

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher w tym MyPagecelu należy zaktualizować kontekst, aby zmienić język na „jp”, jak poniżej

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;

Jak mogę zaktualizować kontekst z poziomu komponentu LanguageSwitcher?

mshameer
źródło
Czytałeś to? facebook.github.io/react/docs/context.html#updating-context Być może jest to coś bardziej odpowiedniego dla stanu, a nie kontekstu
azium
@azium Tak .. W tym dokumencie kontekst jest aktualizowany z samego komponentu lub w dokumencie jest dodany link do bloga, który zawiera kontekst przekazany jako rekwizyty do dostawcy kontekstu. Muszę go zaktualizować z komponentu potomnego
mshameer
Nie, dokument mówi, żeby nie używać kontekstu, jeśli chcesz go zaktualizować. „Nie rób tego”, żeby być precyzyjnym.
Powtórzę
2
@LondonRob jakiego rodzaju kanonicznej odpowiedzi szukasz? IMO zawartość dokumentów wygląda dla mnie dobrze. Jeśli chcesz ustawić kontekst w dziecku, po prostu utwórz ustawiacz w komponencie dostawcy i przekaż go dziecku konsumentowi. Następnie wywołaj tę funkcję ustawiającą w konsumencie podrzędnym i ustaw ją na dowolne dane w dziecku. Nadal zgadza się z pomysłem Reacta dotyczącym podnoszenia danych.
Andrew Li
2
@azium to tylko wskazówka dla innych, którzy czytają ten komentarz przez te wszystkie lata. Aktualizowanie kontekstu z komponentu potomnego jest teraz obsługiwane i całkiem proste: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Odpowiedzi:

250

Korzystanie z haków

Hooki zostały wprowadzone w 16.8.0, więc poniższy kod wymaga minimalnej wersji 16.8.0 (przewiń w dół, aby zobaczyć przykład komponentów klas). CodeSandbox Demo

1. Ustawienie stanu nadrzędnego dla dynamicznego kontekstu

Po pierwsze, aby mieć dynamiczny kontekst, który można przekazać konsumentom, użyję stanu rodzica. Gwarantuje to, że mam jedno źródło prawdy. Na przykład moja aplikacja nadrzędna będzie wyglądać następująco:

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

  return (
    ...
  );
};

languageJest przechowywana w państwie. Później przekażemy obie languagefunkcje i funkcję ustawiającą setLanguageprzez kontekst.

2. Tworzenie kontekstu

Następnie stworzyłem taki kontekst językowy:

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

Tutaj ustawiam wartości domyślne dla language(„en”) i setLanguagefunkcję, która zostanie wysłana przez dostawcę kontekstu do konsumenta (ów). Są to tylko wartości domyślne i podam ich wartości, gdy używam komponentu dostawcy w obiekcie nadrzędnym App.

Uwaga: LanguageContextpozostaje to samo, niezależnie od tego, czy używasz haków, czy komponentów opartych na klasach.

3. Stworzenie konsumenta kontekstowego

Aby przełącznik języka ustawiał język, powinien mieć dostęp do funkcji ustawiania języka poprzez kontekst. Może wyglądać mniej więcej tak:

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

Tutaj ustawiam język na „jp”, ale możesz mieć własną logikę, aby ustawić języki.

4. Owinięcie konsumenta w dostawcę

Teraz wyrenderuję mój komponent do przełączania języków w a LanguageContext.Provideri przekażę wartości, które muszą być wysłane przez kontekst do dowolnego poziomu głębiej. Oto jak wygląda mój rodzic 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>
  );
};

Teraz po każdym kliknięciu przełącznika języka dynamicznie aktualizuje on kontekst.

CodeSandbox Demo

Korzystanie z komponentów klas

Najnowsze API kontekstowe zostało wprowadzone w React 16.3, co zapewnia doskonały sposób na posiadanie dynamicznego kontekstu. Poniższy kod wymaga minimalnej wersji 16.3.0. CodeSandbox Demo

1. Ustawienie stanu nadrzędnego dla dynamicznego kontekstu

Po pierwsze, aby mieć dynamiczny kontekst, który można przekazać konsumentom, użyję stanu rodzica. Gwarantuje to, że mam jedno źródło prawdy. Na przykład moja aplikacja nadrzędna będzie wyglądać następująco:

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

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

  ...
}

Plik language on przechowywany w stanie wraz z metodą ustawiania języka, którą można trzymać poza drzewem stanu.

2. Tworzenie kontekstu

Następnie stworzyłem taki kontekst językowy:

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

Tutaj ustawiam wartości domyślne dla language(„en”) i setLanguagefunkcję, która zostanie wysłana przez dostawcę kontekstu do konsumenta (ów). Są to tylko wartości domyślne i podam ich wartości, gdy używam komponentu dostawcy w obiekcie nadrzędnym App.

3. Stworzenie konsumenta kontekstowego

Aby przełącznik języka ustawiał język, powinien mieć dostęp do funkcji ustawiania języka poprzez kontekst. Może wyglądać mniej więcej tak:

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

Tutaj ustawiam język na „jp”, ale możesz mieć własną logikę, aby ustawić języki.

4. Owinięcie konsumenta w dostawcę

Teraz wyrenderuję mój komponent do przełączania języków w a LanguageContext.Provideri przekażę wartości, które muszą być wysłane przez kontekst do dowolnego poziomu głębiej. Oto jak wygląda mój rodzic 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>
    );
  }
}

Teraz po każdym kliknięciu przełącznika języka dynamicznie aktualizuje on kontekst.

CodeSandbox Demo

Divyanshu Maithani
źródło
jaki jest cel wartości domyślnych, z którymi inicjalizujesz kontekst? Czy te wartości domyślne nie są zawsze zastępowane przez Provider?
ecoe
@ecoe poprawne, jednak w przypadku, gdy dostawca nie przejdzie value, konsument użyje ustawień domyślnych.
Divyanshu Maithani
1
Dlaczego konteksty są ograniczone do ustawienia / uzyskania jednej prostej wartości… Wydaje się to bardzo nieefektywne. Lepszym przykładem byłoby wyróżnienie kontekstu, który ma wartość domyślną jako obiekt i odpowiednie zaktualizowanie obiektu.
AlxVallejo
1
Typescript narzeka w moim przypadku, jeśli setLanguage nie ma parametrów. setLanguage: (language: string) => {}pracuje dla mnie.
alex351
1
Dziękuję Ci za to. To był najwyraźniejszy sposób, który nie sprawił, że chciałem wyrywać włosy.
c0dezer019
49

Powyższa odpowiedź Maithani to świetne rozwiązanie, ale jest to element klasy bez użycia haczyków. Ponieważ React zaleca używanie komponentów funkcjonalnych i hooków, więc zaimplementuję to z hookami useContext i useState. Oto jak zaktualizować kontekst z poziomu komponentu potomnego.

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
Mateen Kiani
źródło
1
Robię to i mój zestaw funkcji w moim komponentu dziecko jest zawsze ten, który początkowo zadeklarował podczas tworzenia kontekstu:() => {}
Alejandro Corredor
5
Aby wyjaśnić, myślę, że w twoim przykładzie state.setLanguage('pk')nic nie zrobię, ponieważ const state = useContext(LanguageContext)znajduje się poza LanguageContextProvider. Rozwiązałem swój problem, przenosząc dostawcę o jeden poziom w górę, a następnie używając useContextu dziecka o jeden poziom niżej.
Alejandro Corredor
2
W przypadku, gdy nie chcesz, aby przenieść kontekstowe jeden poziom wyżej dostawcy mogą również korzystać kontekstowe konsumentowi takiego: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.
Mateen Kiani
3
Bardzo podoba mi się sposób, w jaki organizujesz plik LanguageContextMangement.js. Moim zdaniem to czysty sposób robienia rzeczy i teraz zacznę to robić. Dziękuję Ci!
lustig
2
Dzięki za uznanie, to naprawdę zachęca mnie do kontynuowania takiej pracy!
Mateen Kiani
3

Jednym dość prostym rozwiązaniem jest ustawienie stanu w kontekście przez dołączenie metody setState do dostawcy w następujący sposób:

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

A w swoim kliencie zadzwoń do updateLanguage, takiego jak:

// 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
źródło