Context.Injector
A React component. Simultaneously provides and consumes the observable.
This is just a shorthand for convenience. The following two examples are equivalent:
<Context.Provider>
<Context.Consumer>
{renderProp}
</Context.Consumer>
</Context.Provider>
<Context.Injector>
{renderProp}
</Context.Injector>
Unlike Context.Consumer
, will not throw an error if the Context's Provider is not an ancestor of the injected component. This is because injected components get their own Providers.
Definition
export interface Injector extends Component<{
children: (value: WrappedStateContainer) => ReactNode
onMount?(store: WrappedStateContainer): void
onUnmount?(store: WrappedStateContainer): void
}, {}> {}
Note that the Injector is just a combination of the Context's Provider and Consumer.
children – A render prop! Required. Inherited from Context.Consumer
.
onMount – Optional. Function. Inherited from Context.Provider
.
onUnmount – Optional. Function. Inherited from Context.Provider
.
Use case: Provider-turned-Consumer
Consider the following code:
import React from 'react'
import TodosContext from '../contexts/TodosContext'
import TodoList from './TodoList'
import TodoFilters from './TodoFilters'
const Todos = () => (
<TodosContext.Provider>
<p>
Todos remaining:{' '}
<TodosContext.Consumer>
{store => store.state.length}
</TodosContext.Consumer>
</p>
<TodoList />
<TodoFilters />
</TodosContext.Provider>
)
The <Todos>
component is a wrapper around our entire Todos application. So naturally he encapsulates his children in the Context's <Provider>
. But he is also a consumer! The <p>
tag needs access to the store's state. This tag is small enough that moving it into its own component would be a premature abstraction. But it's still annoying.
Enter the Injector:
const Todos = () => (
<TodosContext.Injector>
{store => (
<p>
Todos remaining: {store.state.length}
</p>
<TodoList />
<TodoFilters />
)}
</TodosContext.Injector>
)
Now the provider accurately reflects the fact that he is also a consumer. Good boy.
Note that we can simplify this further with the Injector's companion inject()
HOC:
const Todos = TodosContext.inject('store')(({ store }) => (
<p>
Todos remaining: {store.state.length}
</p>
<TodoList />
<TodoFilters />
))
Separation of data and UI
Injectors can be used to encourage a strict separation of data and UI. This becomes a replacement for React's local component state management:
import { StoreApi, createContext } from 'react-zedux'
import { createStore } from 'zedux'
class ProfileFormState extends StoreApi {
store = createStore()
.hydrate({
firstName: '',
email: ''
})
setField = ({ currentTarget: { name, value } }) => {
// React Zedux merges the store into `this`, so the store's
// `setState()` method is accessible like so:
this.setState({
[name]: value
})
}
submit = event => {
event.preventDefault()
// some clever ajax stuff here, or something
}
}
export default createContext(ProfileFormState)
.inject(ProfileForm)
function ProfileForm({
submit,
setField,
state: { email, firstName }
}) {
return (
<form onSubmit={submit}>
<input
name="firstName"
onChange={setField}
value={firstName}
/>
<input
name="email"
onChange={setField}
value={email}
/>
</form>
)
}
Do we encourage this? Nope. There's nothing wrong with it, but React's local component state management is fine. Use it first before reaching for other tools!