Reacts new context api has been merged in 16.3.1 and has been kept low-level which makes it very powerful and flexible, but at the same time there are a couple of annoyances you could run into if the api is used naively.
React always had a simple api to communicate changes: setState. Unfortunately it never was feasible for sub-tree changes. Neither passing state down the tree nor compound components could really fix it and there weren't any alternatives. State managers like redux and mobX, valuable in complex situations, changed everything you knew. So you go from simple setState to boilerplate heavy action-creators and intricate observable systems. The old context api was stale, it couldn't communicate changes made to the original object, therefore many libs had to ship their own broadcast mechanisms: redux, mobx, react-router, they all carry that overhead.
The new context api isn't exactly "comfortable" to use (see below), but at least it pretty much paves the way for abstractions that can delegate the workload to React. react-contextual doesn't need broadcasters, scu fillers, diffing engine, bindings, etc. The overhead is actually minimal to elevate setState.
A context provider will re-render its sub-tree every time it changes, it is a component after all. If you plan to wrap your app in a provider like you would do with something like reduxes Provider you need to be aware of it, either using PureComponent or filling shouldComponentUpdate.
react-contextuals Provider
behaves like reduxes in that it communicates changes down its sub-trees without causing components to re-render that shouldn't.
A context consumer wrapped in one or multiple providers can render needlessly, even if the state it is interested in remains the same. Again, safeguarding against it brings its own pitfalls you would have to be aware of.
react-contextual maps context state to component props, similar to reduxes connect. That means you can pick properties or even use memoized selectors. Extending the wrapped component from React.PureComponent
will only render if the selected state has actually changed, even if components sit deeply nested in multiple prividers & consumers that trigger on various state changes.
Used raw the api can cause heavy, visual nesting, every time you consume a providers value, worse if you have to read out several.
react-contextual supports both render props and HOCs, allowing you to select multiple providers at once.
subscribe(
[ThemeContext, UserContext, LanguageContext],
(theme, user, language) => ({ theme, user, language })
)(Component)
As powerful as they might be, they can stretch code and maybe sometimes you'd rather have connected components instead that you can simply place without having to wrap everything. Both have their pros and cons. react-contextual offers better, more selective render props as well as higher order components with the same semantics you know from redux.
React.createContext creates an object that is not tied to a component any longer. Usually it's used as a singleton. That makes it harder if you want to have component-bound (like it used to be with the old api) or dynamic context. That also means you can't re-use a provider in a nested tree as it would overwrite values set by previous instances.
react-contextual solves this by offering a couple of higher order components like namedContext, moduleContext, transformContext and some helper functions.
There are no prescriptions on how to share or distribute context. If you have a provider-component, how do you pass on its context so users can consume it? Do you add it to a components prototype? Do you export it next to your component? It's questionable if the api is meant for sharing at all as there will be conflicting standards on how or where to fetch context.
react-contextual makes sharing straight forward. It maps context internally and allows various ways to reference it: unique keys, component references, dynamic functions that determine context at runtime, and regular React context objects. In most of all cases you would probably want to use moduleContext
and simlpy use the component itself as a reference for subscribe
.
@moduleContext()
class Theme extends React.PureComponent {
render() {
const { context, children } = this.props
return <context.Provider value={{ color: "red", backgroundColor: "yellow" }} children={children} />
}
}
// Refers to the actual class above, no need for a separate context-handle
@subscribe(Theme, theme => ({ color: theme.color }))
class Header extends React.PureComponent {
render() {
// renders only when theme.color changes ...
return <h1 style={{ color: this.props.color }}>hello</h1>
}
}
/*
// Any React context, polyfills work, too (react-broadcast, create-react-context, etc)
@subscribe(GenericReactContext, value => ({ value }))
// Any keyed context (creates by the namedContext hoc)
@namedContext('uniquelyNamedContext')
@subscribe('uniquelyNamedContext', value => ({ value }))
// Any dynamic context
@namedContext(props => props.dynamicallyDerivedKey)
@subscribe(props => props.dynamicallyDerivedKey, value => ({ value }))
// Any created store
const store = createStore(...)
@subscribe(store, value => ({ value }))
*/