in classic React, to make one component's state accessible to a sibling, we need to move state up to the parent and then pass it down via props to both children. so as more components need a certain piece of information from state, we have to lift state further and further up, and pass props further and further down. this is called prop drilling.
React Context allows you to bypass this by passing data directly from an ancestor to a descendant, using the Provider and Consumer wrappers:
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
const { Consumer, Provider } = React.createContext()
const AppWithContext = () => (
<Provider value={"light"}>
{/* now, any component in this subtree can access the context. */}
<App />
</Provider>
)
ReactDOM.render(<AppWithContext />, document.getElementById("root"))
for class components, the simplest way to consume context is to set the contextType
static property. so either MyComponent.contextType = ExampleContext
outside the class, or static contextType = ThemeContext
inside the class:
import React from "react"
import ThemeContext from "./themeContext"
class Button extends Component {
render() {
const theme = this.context
return (
<button className={`${theme}-theme`}>Switch Theme</button>
)
}
}
Button.contextType = ThemeContext
where themeContext.js
just creates a context to be shared across components:
import React from "react"
const ThemeContext = React.createContext()
export default ThemeContext
for functional components, you can access context by rendering the Consumer
component created in React.createContext()
, and passing it a render prop that will have access to context. the render prop must be passed to Consumer
's children
prop:
import React from "react"
import ThemeContext from "./themeContext"
function Button(props) {
return (
<ThemeContext.Consumer>
{theme => (
<button className={`${theme}-theme`}>Switch Theme</button>
)}
</ThemeContext.Consumer>
{/* remember, the above is equivalent to the following:
<ThemeContext.Consumer children={theme => ...} />
(see the [section on React children](<https://www.notion.so/Reusability-407214c480bd4047ab5a4ec8d98200e5#ed3670c7c4e24ac097081732a81f8f94>))
*/}
)
}
export default Button
you don't have to wrap your entire component in the Consumer, you can just wrap the part that actually needs context:
function App() {
return (
<div>
<main>
<UserContext.Consumer>
{username => (
<p className="main">Hello, {username}! 🎉</p>
)}
</UserContext.Consumer>
</main>
</div>
)
}
in a Consuming component, you can do whatever you want with context, including passing it down as props to a child.
of course, you can pass an object to the Provider instead of just a primitive value; the object can include a toggle function to update the context value:
import React, {Component} from "react"
const {Provider, Consumer} = React.createContext()
class ThemeContextProvider extends Component {
state = {
theme: "dark"
}
toggleTheme = () => {
this.setState(prevState => {
return {
theme: prevState.theme === "light" ? "dark" : "light"
}
})
}
render() {
return (
<Provider value={{theme: this.state.theme, toggleTheme: this.toggleTheme}}>
{this.props.children}
</Provider>
)
}
}
export {ThemeContextProvider, Consumer as ThemeContextConsumer}