Bagaimana cara memperbarui React Context dari dalam komponen anak?

100

Saya memiliki pengaturan bahasa dalam konteks seperti di bawah ini

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

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

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

export default LanguageProvider;

Kode lamaran saya akan menjadi seperti di bawah ini

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

Halaman Saya memiliki komponen untuk mengganti bahasa

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher dalam hal ini MyPageperlu memperbarui konteks untuk mengubah bahasa menjadi 'jp' seperti di bawah ini

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;

Bagaimana cara memperbarui konteks dari dalam komponen LanguageSwitcher?

mshameer
sumber
Sudahkah kamu membaca ini? facebook.github.io/react/docs/context.html#updating-context Mungkin ini adalah sesuatu yang lebih cocok untuk negara bukan konteks
azium
@azium Ya .. Dalam dokumen itu konteksnya diperbarui dari komponen itu sendiri atau ada tautan blog yang ditambahkan di dokumen yang berisi konteks yang diteruskan sebagai alat peraga ke penyedia konteks. Saya perlu memperbaruinya dari komponen anak
mshameer
Uhh tidak, dokumen mengatakan untuk tidak menggunakan konteks jika Anda perlu memperbaruinya. Tepatnya, "Jangan lakukan itu". Saya akan tegaskan, gunakan negara bukan konteks
azium
2
@LondonRob, jawaban kanonik seperti apa yang Anda cari? IMO konten dokumen tampak baik-baik saja bagi saya. Jika Anda ingin menyetel konteks pada anak, cukup buat penyetel dalam komponen penyedia dan teruskan itu ke konsumen anak. Kemudian panggil penyetel itu di konsumen anak dan setel ke data apa pun yang ada di dalam anak itu. Masih berpegang pada ide React untuk mengangkat data.
Andrew Li
2
@azium hanya memberitahukan orang lain membaca komentar ini selama bertahun-tahun kemudian. Memperbarui konteks dari komponen anak sekarang didukung dan cukup mudah: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

Jawaban:

227

Menggunakan pengait

Hooks diperkenalkan di 16.8.0 sehingga kode berikut memerlukan versi minimum 16.8.0 (gulir ke bawah untuk contoh komponen kelas). Demo CodeSandbox

1. Menyetel status induk untuk konteks dinamis

Pertama, agar memiliki konteks dinamis yang dapat diteruskan ke konsumen, saya akan menggunakan status induk. Ini memastikan bahwa saya memiliki satu sumber kebenaran yang muncul. Misalnya, Aplikasi orang tua saya akan terlihat seperti ini:

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

  return (
    ...
  );
};

The languagedisimpan di negara bagian. Kami akan melewatkan keduanya languagedan fungsi penyetel setLanguagemelalui konteks nanti.

2. Menciptakan konteks

Selanjutnya, saya membuat konteks bahasa seperti ini:

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

Di sini saya menyetel default untuk language('en') dan setLanguagefungsi yang akan dikirim oleh penyedia konteks ke konsumen. Ini hanya default dan saya akan memberikan nilainya saat menggunakan komponen provider di induk App.

Catatan: LanguageContexttetap sama apakah Anda

3. Menciptakan konsumen konteks

Agar pengalih bahasa mengatur bahasa, ia harus memiliki akses ke fungsi penyetel bahasa melalui konteks. Ini bisa terlihat seperti ini:

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

Di sini saya hanya mengatur bahasa ke 'jp' tetapi Anda mungkin memiliki logika Anda sendiri untuk mengatur bahasa untuk ini.

4. Membungkus konsumen dalam penyedia

Sekarang saya akan membuat komponen pengalih bahasa saya di a LanguageContext.Providerdan meneruskan nilai-nilai yang harus dikirim melalui konteks ke tingkat yang lebih dalam. Begini Apppenampilan orang tua saya :

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

Sekarang, setiap kali pengalih bahasa diklik itu memperbarui konteks secara dinamis.

Demo CodeSandbox

Menggunakan komponen kelas

API konteks terbaru diperkenalkan di React 16.3 yang menyediakan cara yang bagus untuk memiliki konteks dinamis. Kode berikut membutuhkan versi minimum 16.3.0. Demo CodeSandbox

1. Menyetel status induk untuk konteks dinamis

Pertama, agar memiliki konteks dinamis yang dapat diteruskan ke konsumen, saya akan menggunakan status induk. Ini memastikan bahwa saya memiliki satu sumber kebenaran yang muncul. Misalnya, Aplikasi orang tua saya akan terlihat seperti ini:

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

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

  ...
}

Itu languagedisimpan di negara bagian bersama dengan metode penyetel bahasa, yang mungkin Anda simpan di luar pohon negara.

2. Menciptakan konteks

Selanjutnya, saya membuat konteks bahasa seperti ini:

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

Di sini saya menyetel default untuk language('en') dan setLanguagefungsi yang akan dikirim oleh penyedia konteks ke konsumen. Ini hanya default dan saya akan memberikan nilainya saat menggunakan komponen provider di induk App.

3. Menciptakan konsumen konteks

Agar pengalih bahasa mengatur bahasa, ia harus memiliki akses ke fungsi penyetel bahasa melalui konteks. Ini bisa terlihat seperti ini:

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

Di sini saya hanya mengatur bahasa ke 'jp' tetapi Anda mungkin memiliki logika Anda sendiri untuk mengatur bahasa untuk ini.

4. Membungkus konsumen dalam penyedia

Sekarang saya akan membuat komponen pengalih bahasa saya di a LanguageContext.Providerdan meneruskan nilai-nilai yang harus dikirim melalui konteks ke tingkat yang lebih dalam. Begini Apppenampilan orang tua saya :

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

Sekarang, setiap kali pengalih bahasa diklik itu memperbarui konteks secara dinamis.

Demo CodeSandbox

Divyanshu Maithani
sumber
apa tujuan dari nilai default yang Anda gunakan untuk menginisialisasi konteks? Bukankah default tersebut selalu diganti dengan Provider?
ecoe
@ecoe benar, namun jika penyedia memberikan no value, default akan digunakan oleh konsumen.
Divyanshu Maithani
1
Mengapa konteks dibatasi untuk menetapkan / mendapatkan satu nilai sederhana .... Itu tampaknya sangat tidak efisien. Contoh yang lebih baik adalah menyorot konteks yang memiliki default sebagai objek dan memperbarui objek yang sesuai.
AlxVallejo
Anda dapat menggunakan konteks untuk apa yang Anda sebutkan. Contoh jawaban adalah return berdasarkan soal. Oleh karena itu, ini hanya berisi satu nilai untuk singkatnya.
Divyanshu Maithani
1
Ketikan mengeluh dalam kasus saya jika setLanguage tidak memiliki parameter. setLanguage: (language: string) => {}bekerja untuk saya.
alex351
48

Jawaban di atas oleh Maithani adalah solusi yang bagus tetapi itu adalah komponen kelas tanpa menggunakan pengait. Karena direkomendasikan oleh React untuk menggunakan komponen fungsional dan hook jadi saya akan mengimplementasikannya dengan hook useContext dan useState. Berikut adalah bagaimana Anda bisa memperbarui konteks dari dalam komponen anak.

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
sumber
1
Saya melakukan ini dan fungsi set saya di dalam komponen anak saya selalu yang pertama kali kami nyatakan saat membuat konteks:() => {}
Alejandro Corredor
4
Untuk memperjelas, saya pikir dalam contoh Anda state.setLanguage('pk')tidak akan melakukan apa-apa, karena const state = useContext(LanguageContext)berada di luar LanguageContextProvider. Saya memecahkan masalah saya dengan memindahkan penyedia satu tingkat ke atas dan kemudian menggunakan useContextpada anak satu tingkat di bawah.
Alejandro Corredor
2
Dalam kasus jika Anda tidak ingin pindah konteks penyedia satu tingkat ke atas juga dapat digunakan konsumen konteks seperti ini: <LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>.
Mateen Kiani
3
Saya sangat menyukai cara Anda mengatur file LanguageContextMangement.js. Menurut pendapat saya, itu cara bersih dalam melakukan sesuatu dan saya akan mulai melakukannya sekarang. Terima kasih!
lustig
2
Terima kasih atas apresiasi yang sangat mendorong saya untuk terus melakukan pekerjaan seperti ini!
Mateen Kiani
2

Salah satu solusi yang cukup sederhana adalah menyetel status pada konteks Anda dengan menyertakan metode setState di penyedia Anda seperti:

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

Dan di konsumen Anda, panggil updateLanguage seperti ini:

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