Reputation: 4141
I have the language settings in the context as like below
class LanguageProvider extends Component {
static childContextTypes = {
langConfig: PropTypes.object,
};
getChildContext() {
return { langConfig: 'en' };
}
render() {
return this.props.children;
}
}
export default LanguageProvider;
My application code will be something like below
<LanguageProvider>
<App>
<MyPage />
</App>
</LanguageProvider>
My Page is having a component to switch the language
<MyPage>
<LanguageSwitcher/>
</MyPage>
LanguageSwitcher
in this MyPage
need to update the context to change the language into 'jp' as below
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;
How can I update the context from inside the LanguageSwitcher component ?
Upvotes: 333
Views: 291996
Reputation: 10526
I personally like this pattern:
File: context.jsx
import React from 'react';
// The Context
const TemplateContext = React.createContext({});
// Template Provider
const TemplateProvider = ({children}) => {
const [myValue, setMyValue] = React.useState(0);
// Context values passed to consumer
const value = {
myValue, // <------ Expose Value to Consumer
setMyValue // <------ Expose Setter to Consumer
};
return (
<TemplateContext.Provider value={value}>
{children}
</TemplateContext.Provider>
)
}
// Template Consumer
const TemplateConsumer = ({children}) => {
return (
<TemplateContext.Consumer>
{(context) => {
if (context === undefined) {
throw new Error('TemplateConsumer must be used within TemplateProvider');
}
return children(context)
}}
</TemplateContext.Consumer>
)
}
// useTemplate Hook
const useTemplate = () => {
const context = React.useContext(TemplateContext);
if(context === undefined)
throw new Error('useTemplate must be used within TemplateProvider');
return context;
}
export {
TemplateProvider,
TemplateConsumer,
useTemplate
}
Then you can create a functional component, if it is a child in the tree of the provider:
File: component.jsx
import React from 'react';
import {useTemplate} from 'context.jsx';
const MyComponent = () => {
// Get the value and setter from the consumer hook
const {myValue, setMyValue} = useTemplate();
// Demonstrate incrementing the value
React.useEffect(() => {
// Increment, set in context
const increment = () => setMyValue(prev => prev + 1);
// Increment every second
let interval = setInterval(increment, 1000);
// Cleanup, kill interval when unmounted
return () => clearInterval(interval);
},[]) // On mount, no dependencies
// Render the value as it is pulled from the context
return (
<React.Fragment>
Value of MyValue is: {myValue}
</React.Fragment>
)
}
Upvotes: 23
Reputation: 71
/ context.js
Setup global context file
import { createContext } from 'react'
export const Context = createContext()
/ parent.js
import { useState } from 'react'
import { Context } from './context'
import Child from './child.js'
export default function Parent() {
const [context, setContext] = useState('default context value')
return <>
<Context.Provider value={[context, setContext]}>
<Child/>
</Context.Provider>
</>
}
/ child.js
Inside child.js you can use setContext which affects both of parent.js and child.js
import { useContext } from 'react'
import { Context } from './context'
export default function Child() {
const [context, setContext] = useContext(Context)
setContext('New value set by child')
return <>
...
</>
}
Reference: How to useContext and set value of context in child components in 3 steps
Upvotes: 7
Reputation: 130570
context
:useContext
(see "mid-level" in below code example)Provider
value
prop (for the context), derived from previous context value (via useContext
)Read the below example code from bottom-to-top, starting from:
render stage 👉 App
component 👉 MidLevel
component 👉 Dummy
component
const {useState, Fragment, createContext, useContext, Provider} = React
// create a context
const MyContext = React.createContext()
// Dummy - a simple component which uses the context
const Dummy = () => {
const ctx = useContext(MyContext)
// print contexy
return <p>
Context value:
<mark>{JSON.stringify(ctx)}</mark>
</p>
}
// Some mid-level component
const MidLevel = () => {
const ctx = useContext(MyContext)
// update ancestor context
return <MyContext.Provider value={{...ctx, foo: 2, bar: 4}}>
<Dummy/>
</MyContext.Provider>
}
// Top-level component (with default context value)
const App = () => <MyContext.Provider value={{ foo: 1, baz: 3 }}>
<MidLevel/>
<Dummy/>
</MyContext.Provider>
// Render
ReactDOM.render(<App />, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" style="font: 20px Arial"></div>
Upvotes: 2
Reputation: 1912
Here's my approach, based on @Nicholas Hamilton's answer but for TypeScript and applying @LIIT recommendations.
Note: I'm using Next.js and a highly opinionated setup for ESLint.
import { createContext, useContext, useMemo, useState, Dispatch, SetStateAction } from "react"
interface TemplateContextProps {
myValue: number | null
setMyValue: Dispatch<SetStateAction<number | null>>
}
const TemplateContext = createContext<TemplateContextProps>({
myValue: null,
setMyValue: (prevState: SetStateAction<number | null>) => prevState,
})
interface TemplateProviderProps {
children: React.ReactNode
}
function TemplateProvider({ children }: TemplateProviderProps): JSX.Element {
const [myValue, setMyValue] = useState<number | null>(null)
const value = useMemo(() => ({ myValue, setMyValue }), [myValue, setMyValue])
return <TemplateContext.Provider value={value}>{children}</TemplateContext.Provider>
}
const TemplateConsumer = TemplateContext.Consumer
const useTemplate = () => useContext(TemplateContext)
export { TemplateProvider, TemplateConsumer, useTemplate }
I like to initialize the value to null, it's a more dynamic approach, but you can restrict the type to just numbers and set it to 0 by default.
Upvotes: 10
Reputation: 604
Just wanted to add to Divyanshu Maithani's answer that it's generally safer to use useMemo
when wrapping the consumer in a provider.
const App = () => {
const [language, setLanguage] = useState("en");
const value = useMemo(
() => ({ language, setLanguage }),
[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>
);
};
from react/jsx-no-constructed-context-values rule :
React Context, and all its child nodes and Consumers are rerendered whenever the value prop changes. Because each Javascript object carries its own identity, things like object expressions ({foo: 'bar'}) or function expressions get a new identity on every run through the component. This makes the context think it has gotten a new object and can cause needless rerenders and unintended consequences.
This can be a pretty large performance hit because not only will it cause the context providers and consumers to rerender with all the elements in its subtree, the processing for the tree scan react does to render the provider and find consumers is also wasted.
Upvotes: 0
Reputation: 3293
Since it is recommended by React to use functional components and hooks so I will implement it with useContext and useState hooks. Here is how you can update the context from within a child component.
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
Upvotes: 112
Reputation: 15016
Hooks were introduced in 16.8.0 so the following code requires a minimum version of 16.8.0 (scroll down for the class components example). CodeSandbox Demo
Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:
const App = () => {
const [language, setLanguage] = useState("en");
const value = { language, setLanguage };
return (
...
);
};
The language
is stored in the state. We will pass both language
and the setter function setLanguage
via context later.
Next, I created a language context like this:
// set the defaults
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
Here I'm setting the defaults for language
('en') and a setLanguage
function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App
.
Note: the LanguageContext
remains same whether you use hooks or class based components.
In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:
const LanguageSwitcher = () => {
const { language, setLanguage } = useContext(LanguageContext);
return (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
);
};
Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.
Now I'll render my language switcher component in a LanguageContext.Provider
and pass in the values which have to be sent via context to any level deeper. Here's how my parent App
look like:
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>
);
};
Now, whenever the language switcher is clicked it updates the context dynamically.
The latest context API was introduced in React 16.3 which provides a great way of having a dynamic context. The following code requires a minimum version of 16.3.0. CodeSandbox Demo
Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:
class App extends Component {
setLanguage = language => {
this.setState({ language });
};
state = {
language: "en",
setLanguage: this.setLanguage
};
...
}
The language
is stored in the state along with a language setter method, which you may keep outside the state tree.
Next, I created a language context like this:
// set the defaults
const LanguageContext = React.createContext({
language: "en",
setLanguage: () => {}
});
Here I'm setting the defaults for language
('en') and a setLanguage
function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App
.
In order to have the language switcher set the language, it should have the access to the language setter function via context. It can look something like this:
class LanguageSwitcher extends Component {
render() {
return (
<LanguageContext.Consumer>
{({ language, setLanguage }) => (
<button onClick={() => setLanguage("jp")}>
Switch Language (Current: {language})
</button>
)}
</LanguageContext.Consumer>
);
}
}
Here I'm just setting the language to 'jp' but you may have your own logic to set languages for this.
Now I'll render my language switcher component in a LanguageContext.Provider
and pass in the values which have to be sent via context to any level deeper. Here's how my parent App
look like:
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>
);
}
}
Now, whenever the language switcher is clicked it updates the context dynamically.
Upvotes: 732
Reputation: 606
One quite simple solution is to set state on your context by including a setState method in your provider like so:
return (
<Context.Provider value={{
state: this.state,
updateLanguage: (returnVal) => {
this.setState({
language: returnVal
})
}
}}>
{this.props.children}
</Context.Provider>
)
And in your consumer, call updateLanguage like so:
// 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>
Upvotes: 4