README
with-context-react
A much easier way to access a context from within a class component (and functional components too) by using the new
useContext
API behind the scenes.
Why use it
Accessing a React context from within a class component is often times very dull and annoying, especially when trying to use multiple context and providers. This package simplifies the proccess by simply wrapping your component with a function defined as withContext(...)
, or wrapping your JSX Element Constructors (classes) inside another component defined as <WithContext ... />
, while only having to pass the context as an argument or prop, respectively. The context will be sent as a prop defined as _context
(e.g. this.props._context
) - keep in mind you need to have a context provider present somewhere within your application wrapping these elements, it is also possible to pass a provider as an argument to withContext
or as a prop to <WithContext />
.
You may also pass a context provider and save yourself some time spent wrapping your app or other components as well, but keep in mind that this will create a new scope for that context - which might be beneficial or detrimental depending on the scenario. Either way, I suggest taking a look at the code snippets more more information about this.
If the intention is to pass multiple contexts (and providers), then you may do so by using the export withContexts(...)
which works similarly to how withContext
works while supporting multiple contexts and providers. Similarly, <WithContexts ... />
is a component that passes down the contexts to all wrapped constructors as props (within the first scope level), while also setting up every passed provider as higher order component (HOC), if any.
Install
npm install --save with-context-react
Showcase
Usage
We'll use a package called react-png-button
which includes a React context and a provider to showcase how both withContext
and <WithContext />
can come into play. I suggest checking out the showcase referenced above and also looking at the react dev tools to see how the providers are rendered.
We can also pass multiple contexts and providers by using withContexts
and <WithContexts />
, both of these work similarly to their respective counterparts mentioned above. To do this, I have done a context that changes the class of the <body>
HTML element, from a dark theme to a light theme and vice versa, here's the source code for that context.
withContext
to wrap a single class (or functional) component
Using Let's assume there is a provider wrapping the whole application, since no provider will be passed to withContext
as an argument, then it will fallback to the previously mentioned "global" provider's context scope.
withContext(WrappedComponent, Context)
:
import React, { Component } from 'react'
import propTypes from 'prop-types'
// JSX
import { withContext } from 'with-context-react'
import Button, { Context } from 'react-png-button'
class App extends Component {
static propTypes = {
_context: propTypes.object
}
accessContextMethods = () => {
this.props._context.setStyle({
padding: '8px',
textTransform: 'uppercase',
borderRadius: 'none',
backgroundColor: 'goldenrod'
})
}
render () {
console.log(this.props._context)
return (
<div>
<div style={{ margin: '12px 0' }}>Open your dev-tools console log!</div>
<Button onClick={this.accessContextMethods}>Click to change styles!</Button>
</div>
)
}
}
export default withContext(App, Context)
withContext
to wrap a single class (or functional) component while also passing a React context provider
Using In this example we pass a Provider
to withContext
as an argument, as well as the context of course, then said context and any functionality will only work within that provider's context scope.
withContext(WrappedComponent, Context, Provider)
:
import React, { Component } from 'react'
import propTypes from 'prop-types'
// CSS
import classes from './exampleClasses.module.css'
// JSX
import { withContext } from 'with-context-react'
import Button, { Context, Provider } from 'react-png-button'
class App extends Component {
static propTypes = {
_context: propTypes.object
}
initialButtonClass = this.props._context.className
setClass = () => {
this.props._context.setGlobalClassName(classes.Button)
}
resetClass = () => {
this.props._context.setGlobalClassName(this.initialButtonClass)
}
render () {
console.log(this.props._context)
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<div style={{ margin: '12px 0' }}>Open your dev-tools console log!</div>
<Button onClick={this.setClass}>Click to change styles!</Button>
<br />
<Button onClick={this.resetClass}>Back to normal!</Button>
</div>
)
}
}
export default withContext(App, Context, Provider)
- JSX Element <WithContext context={Context} />
<WithContext context={Context} />
works just like withContext
, since no provider was passed to WithContext
as a prop, then it will fallback to the previously mentioned "global" provider's context. All the top level components will receive the context as a prop defined as _context
, on top of receiving their respective props without any side-effects.
<WithContext context={Context} />
:
import React, { Component } from 'react'
// JSX
import { WithContext } from 'with-context-react'
import { Context } from 'react-png-button'
import ClassComponent from './ClassComponent'
class App extends Component {
render () {
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<div style={{ margin: '12px 0' }}>Open your dev-tools console log!</div>
<WithContext context={Context}>
<ClassComponent title={'First Class Component!'} />
<ClassComponent title={'Second Class Component!'} />
</WithContext>
</div>
)
}
}
export default App
/**
* ----------------------------
* -------ClassComponent-------
* ----------------------------
*/
import React, { Component } from 'react'
import propTypes from 'prop-types'
// JSX
import Button from 'react-png-button'
export default class ClassfulComponent extends Component {
static propTypes = {
title: propTypes.string,
_context: propTypes.object
}
setStyle = () => {
this.props._context.setStyle({
padding: '8px',
textTransform: 'uppercase',
borderRadius: 'none',
backgroundColor: 'indigo',
color: 'white'
})
}
resetStyle = () => {
this.props._context.setStyle(undefined)
}
render () {
console.log(this.props)
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<h3>{this.props.title}</h3>
<Button onClick={this.setStyle}>Click to change styles!</Button>
<br />
<Button onClick={this.resetStyle}>Back to normal!</Button>
</div>
)
}
}
- JSX Element with a Provider <WithContext context={Context} provider={Provider} />
Finally just like before, <WithContext context={Context} provider={Provider} />
works just like withContext
when a provider was passed as an argument, except this time it will be a prop.
Any functionality will only work within that provider's context scope. All the top level components will receive the context as a prop defined as _context
, on top of receiving their respective props without any side-effects.
<WithContext context={Context} provider={Provider} />
:
import React, { Component } from 'react'
// JSX
import { WithContext } from 'with-context-react'
import { Context, Provider } from 'react-png-button'
import ClassComponent from './ClassComponent_WithProvider'
class App extends Component {
render () {
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<div style={{ margin: '12px 0' }}>Open your dev-tools console log!</div>
<WithContext context={Context} provider={Provider}>
<ClassComponent title={'First Class Component!'} />
<ClassComponent title={'Second Class Component!'} />
</WithContext>
</div>
)
}
}
export default App
/**
* ----------------------------
* -------ClassComponent-------
* ----------------------------
*/
import React, { Component } from 'react'
import propTypes from 'prop-types'
// CSS
import classes from './exampleClasses.module.css'
// JSX
import Button from 'react-png-button'
export default class ClassfulComponent extends Component {
static propTypes = {
title: propTypes.string,
_context: propTypes.object
}
initialButtonClass = this.props._context.className
setStyle = () => {
this.props._context.setStyle({
padding: '8px',
textTransform: 'uppercase',
borderRadius: 'none',
backgroundColor: 'mediumaquamarine',
color: 'white'
})
this.props._context.setGlobalClassName(classes.Button)
}
resetStyle = () => {
this.props._context.setStyle(undefined)
this.props._context.setGlobalClassName(this.initialButtonClass)
}
render () {
console.log(this.props)
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<h3>{this.props.title}</h3>
<Button onClick={this.setStyle}>Click to change styles!</Button>
<br />
<Button onClick={this.resetStyle}>Back to normal!</Button>
</div>
)
}
}
- Functional wrapper for multiple Contexts & Providers withContexts(WrappedComponent, {...Contexts}, [...Providers])
By using withContext
we can set up multiple contexts and providers in one go, by passing them as arguments.
The argument Contexts has to be an object (example of the structure below), where the object keys will define the name of the props passed down and the values must be the respective contexts, whereas the Providers may be an array containing all of the respective providers.
As before, any functionality will only work within that provider's context scope.
Structure of the Contexts
object passed as an argument to withContexts
:
interface IContexts {
[propName: string]: React.Context<any>
}
// e.g.
withContexts(WrappedComponent, {...Contexts}, [...Providers])
:
import React, { Component } from 'react'
import propTypes from 'prop-types'
// JSX
import { withContexts } from 'with-context-react'
import ThemeProvider, { Context as ThemeContext } from '../themeContext'
import Button, { Context, Provider } from 'react-png-button'
class App extends Component {
static propTypes = {
buttonContext: propTypes.object,
themeContext: propTypes.object
}
setTheme = () => {
this.props.themeContext.setTheme()
}
setStyle = () => {
this.props.buttonContext.setStyle({ padding: '8px', textTransform: 'uppercase', borderRadius: 'none', backgroundColor: 'goldenrod' })
}
resetStyle = () => {
this.props.buttonContext.setStyle(undefined)
}
render () {
console.log('inside withContexts.js', this.props)
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<Button onClick={this.setStyle}>Click to change styles!</Button>
<br />
<Button onClick={this.resetStyle}>Back to normal!</Button>
<br />
<Button onClick={this.setTheme}>Change the page themes from dark to light or vice versa!</Button>
</div>
)
}
}
export default withContexts(App, { buttonContext: Context, themeContext: ThemeContext }, [Provider, ThemeProvider])
- JSX Element with multiple Contexts & Providers <WithContexts contexts={{...Contexts}} providers={[...Providers]} />
By using <WithContexts contexts={{ ...Context }} providers={[ ...Provider ]} /
>, we can set up multiple contexts with their respective providers (if passed) similarly to withContexts, the difference is that WithContext
will pass down the context as a prop defined by the respective key of the context object (structure above) to all of the top level components.
import React, { Component } from 'react'
// JSX
import { WithContexts } from 'with-context-react'
import { Context as ButtonContext, Provider as ButtonProvider } from 'react-png-button'
import ThemeProvider, { Context as ThemeContext } from '../themeContext'
import ClassComponent from './ClassComponent_WithContexts'
class App extends Component {
render () {
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<WithContexts
providers={[ButtonProvider, ThemeProvider]}
contexts={{ buttonContext: ButtonContext, themeContext: ThemeContext }}>
<ClassComponent title={'First Class Component (while using WithContexts)'} />
<ClassComponent title={'Second Class Component (while using WithContexts)'} />
</WithContexts>
</div>
)
}
}
export default App
/**
* ----------------------------
* -------ClassComponent-------
* ----------------------------
*/
import React, { Component } from 'react'
import propTypes from 'prop-types'
// JSX
import Button from 'react-png-button'
export default class App extends Component {
static propTypes = {
title: propTypes.string,
buttonContext: propTypes.object,
themeContext: propTypes.object
}
setTheme = () => {
this.props.themeContext.setTheme()
}
setStyle = () => {
this.props.buttonContext.setStyle({
padding: '8px',
textTransform: 'uppercase',
borderRadius: 'none',
backgroundColor: 'lightblue',
color: '#484848'
})
}
resetStyle = () => {
this.props.buttonContext.setStyle(undefined)
}
render () {
console.log('inside WithContexts.js', this.props)
return (
<div style={{ display: 'flex', justifyContent: 'center', flexFlow: 'column', alignItems: 'center' }}>
<h3>{this.props.title}</h3>
<Button onClick={this.setStyle}>Click to change styles!</Button>
<br />
<Button onClick={this.resetStyle}>Back to normal!</Button>
<br />
<Button onClick={this.setTheme}>Change the page themes from dark to light or vice versa!</Button>
</div>
)
}
}
License
MIT © rmolinamir