New React Context API


I like new react API for creating and using context. It feels right. One thing I immediately tried to do was creating a helper which simplifies consuming multiple contexts. Because they create context hell. When you component uses more than one context and because of a children as a function pattern (which is basically the same as render props pattern where component’s children function is specified as a component’s prop named render), code gets familiar indent to the left (callback hell).

For example with the use of this helper,

<Theme.Consumer>
    { theme => (
        <Language.Consumer>
            { language => (
                <App theme={theme} language={language} />
            )}
        </Language.Consumer>    
    )}
</Theme.Consumer>

would become

<Consume contexts={[Theme, Language]}>
    { (theme, language) => (
        <App theme={theme} language={language} />
    )}  
</Consume>

But after giving it a second thought, I realized in high-performance web applications this “multiple contexts consumer” case scenario can be a bad idea in most cases. Because If we have for example two contexts that are always used together, it’s better to just have one context anyway. But if a component uses more contexts which aren’t co-dependent, that means parts of component will be re-rendered unnecessary. So the component should be split into components which uses only one context or co-dependant contexts.

If you are interested in Consumer component, you can see it in following snippet with example usage. I’ve also added Provide component which mirrors Consume component for the sake of completeness. It’s no problem though if you specify Providers separately or not at all – in that case it will use context’s default value than).


import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import createContext from 'create-react-context'
const Theme = createContext("light")
const Language = createContext("en")
const Whatever = createContext("whatever")
const Consume = ({contexts, children}) => {
const values = []
const _consume = ctxs => {
const [First, …rest] = ctxs
return (
<First.Consumer>
{value => {
values.push(value)
return rest.length ? _consume(rest) : children(…values)
}}
</First.Consumer>
)
}
return _consume(contexts)
}
const Provide = ({contexts, children}) => {
return contexts.reduce((res, [Context, value]) => {
return React.createElement(Context.Provider, {
children: res,
value
})
}, children);
}
class Root extends Component {
constructor(props) {
super(props)
this.state = {
language: 'sl',
theme: 'dark'
}
this.toggleTheme = this.toggleTheme.bind(this)
this.toggleLanguage = this.toggleLanguage.bind(this)
}
toggleLanguage() {
return this.setState(state => ({
language: state.language === "en" ? "sl": "en"
}))
}
toggleTheme() {
return this.setState(state => ({
theme: state.theme === "light" ? "dark": "light"
}))
}
render() {
const contexts = [
[Theme, this.state.theme],
[Language, this.state.language]
]
return (
<Provide contexts={contexts}>
<Consume contexts={[Theme, Language, Whatever]}>
{(theme, language, whatever) => (
<App theme={theme} language={language} whatever={whatever} />
)}
</Consume>
<button onClick={this.toggleTheme}>Toggle theme</button>
<button onClick={this.toggleLanguage}>Toggle language</button>
</Provide>
)
}
}
ReactDOM.render(<Root/>, document.getElementById('root'));

view raw

index.js

hosted with ❤ by GitHub