Como atualizar o React Context de dentro de um componente filho?

109

Eu tenho as configurações de idioma no contexto como abaixo

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

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

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

export default LanguageProvider;

Meu código de aplicativo será algo como abaixo

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

Minha página está tendo um componente para mudar o idioma

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher nesta MyPagenecessidade de atualizar o contexto para mudar o idioma para 'jp' como abaixo

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;

Como posso atualizar o contexto de dentro do componente LanguageSwitcher?

mshameer
fonte
Você leu isso? facebook.github.io/react/docs/context.html#updating-context Talvez isso seja algo mais adequado para o estado, não o contexto
azium
@azium Sim .. Nesse documento, o contexto é atualizado a partir do próprio componente ou há um link de blog adicionado ao documento que contém o contexto passado como adereços para o provedor de contexto, preciso atualizá-lo do componente filho
mshameer
Uhh não, o documento diz para não usar contexto se você precisar atualizá-lo. "Não faça isso" para ser mais preciso. Vou reiterar, use o estado, não o contexto
azium
2
@LondonRob que tipo de resposta canônica você está procurando? IMO, o conteúdo dos documentos parece muito bom para mim. Se você deseja definir o contexto em um filho, basta criar um setter no componente do provedor e passá-lo para um consumidor filho. Em seguida, chame esse configurador no consumidor filho e configure-o para quaisquer dados que estejam dentro do filho. Ainda mantém a ideia do React de levantar dados.
Andrew Li
2
@azium só avisa os outros que lerem este comentário todos esses anos depois. Atualizar o contexto de um componente filho agora é suportado e bastante simples: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Respostas:

249

Usando ganchos

Os ganchos foram introduzidos em 16.8.0, portanto, o código a seguir requer uma versão mínima de 16.8.0 (role para baixo para ver o exemplo de componentes de classe). CodeSandbox Demo

1. Definir o estado pai para contexto dinâmico

Em primeiro lugar, para ter um contexto dinâmico que possa ser passado aos consumidores, usarei o estado do pai. Isso garante que eu tenha uma única fonte de verdade. Por exemplo, meu aplicativo pai terá a seguinte aparência:

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

  return (
    ...
  );
};

o language é armazenado no estado. Passaremos ambos languagee a função setter setLanguagevia contexto posteriormente.

2. Criação de um contexto

Em seguida, criei um contexto de linguagem como este:

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

Aqui estou definindo os padrões para language('en') e umsetLanguage função que será enviada pelo provedor de contexto ao (s) consumidor (es). Esses são apenas padrões e irei fornecer seus valores ao usar o componente de provedor no pai App.

Note o LanguageContext permanece o mesmo se você usar ganchos ou componentes baseados em classe.

3. Criação de um consumidor de contexto

Para que o alternador de idioma defina o idioma, ele deve ter acesso à função configurador de idioma via contexto. Pode ser parecido com isto:

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

Aqui estou apenas definindo o idioma para 'jp', mas você pode ter sua própria lógica para definir os idiomas para isso.

4. Envolvendo o consumidor em um provedor

Agora, vou renderizar meu componente de alternador de idioma em a LanguageContext.Providere passar os valores que devem ser enviados via contexto para qualquer nível mais profundo. É assim que meus pais se Appparecem:

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>
  );
};

Agora, sempre que o alternador de idioma é clicado, ele atualiza o contexto dinamicamente.

CodeSandbox Demo

Usando componentes de classe

A API de contexto mais recente foi introduzida no React 16.3, que fornece uma ótima maneira de ter um contexto dinâmico. O código a seguir requer uma versão mínima de 16.3.0. CodeSandbox Demo

1. Definir o estado pai para contexto dinâmico

Em primeiro lugar, para ter um contexto dinâmico que possa ser passado aos consumidores, usarei o estado do pai. Isso garante que eu tenha uma única fonte de verdade. Por exemplo, meu aplicativo pai terá a seguinte aparência:

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

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

  ...
}

O languageé armazenado no estado junto com um método configurador de idioma, que você pode manter fora da árvore de estados.

2. Criação de um contexto

Em seguida, criei um contexto de linguagem como este:

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

Aqui estou definindo os padrões para language('en') e umasetLanguage função que será enviada pelo provedor de contexto ao (s) consumidor (es). Esses são apenas padrões e irei fornecer seus valores ao usar o componente de provedor no pai App.

3. Criação de um consumidor de contexto

Para que o alternador de idioma defina o idioma, ele deve ter acesso à função configurador de idioma via contexto. Pode ser parecido com isto:

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

Aqui estou apenas definindo o idioma para 'jp', mas você pode ter sua própria lógica para definir os idiomas para isso.

4. Envolvendo o consumidor em um provedor

Agora, vou renderizar meu componente de alternador de idioma em a LanguageContext.Providere passar os valores que devem ser enviados via contexto para qualquer nível mais profundo. É assim que meus pais se Appparecem:

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>
    );
  }
}

Agora, sempre que o alternador de idioma é clicado, ele atualiza o contexto dinamicamente.

CodeSandbox Demo

Divyanshu Maithani
fonte
qual é o propósito dos valores padrão com os quais você inicializa o contexto? Esses padrões não são sempre substituídos pelo Provider?
ecoe
@ecoe correto, porém caso o provedor passe não value, os padrões seriam utilizados pelo consumidor.
Divyanshu Maithani
1
Por que os contextos estão sendo limitados para definir / obter um valor simples ... Isso parece muito ineficiente. Um exemplo melhor seria destacar um contexto que tem o padrão como um objeto e atualizar o objeto de acordo.
AlxVallejo
1
O typescript reclama no meu caso se setLanguage não tiver parâmetros. setLanguage: (language: string) => {}funciona para mim.
alex351
1
Obrigado por isso. Este foi o como fazer mais claro que não me fez querer arrancar o cabelo.
c0dezer019
49

A resposta de Maithani acima é uma ótima solução, mas é um componente de classe sem o uso de ganchos. Como o React recomenda o uso de componentes funcionais e ganchos , irei implementá-lo com os ganchos useContext e useState. Aqui está como você pode atualizar o contexto de um componente filho.

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
fonte
1
Estou fazendo isso e minha função set dentro do meu componente filho é sempre aquela que declaramos inicialmente ao criar o contexto:() => {}
Alejandro Corredor
5
Pra esclarecer, acho que no seu exemplo state.setLanguage('pk')não vai adiantar nada, já que const state = useContext(LanguageContext)está fora do LanguageContextProvider. Resolvi meu problema movendo o provedor um nível acima e, em seguida, usando useContextem uma criança um nível abaixo.
Alejandro Corredor
2
No caso, se você não quer mover seu contexto nível de provedor de um up também pode usar o contexto do consumidor como este: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.
Mateen Kiani
3
Eu realmente gosto da maneira como você organiza o arquivo LanguageContextMangement.js. Essa é uma maneira limpa de fazer as coisas, na minha opinião, e vou começar a fazer isso agora. Obrigado!
lustig
2
Obrigado pela apreciação realmente me incentiva a continuar fazendo um trabalho como este!
Mateen Kiani
3

Uma solução bastante simples é definir o estado em seu contexto, incluindo um método setState em seu provedor, assim:

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

E em seu consumidor, chame updateLanguage assim:

// 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
fonte