This small library allows you to:
-
Build your entire app's view layer first - i.e., all your components become as "dumb" as possible.
-
Decorate your components with
@provide, which allows you to specify - aspropTypes- exactly the data and actions said components need fromredux, using as many stores and/or combining providers as necessary. Providers are automatically assigned to the components that need them. -
Pass context as props at any level (though typically only the root component is necessary) to any component decorated with
@provide.
- Maximum separation of concerns.
- Use packaged providers for common use-cases or even build apps around specific providers.
- Quickly and easily switch out one provider for another.
- Very easily mix and match components from multiple applications whose components use
@provide. - Enforces clean and efficient design.
- Extremely predictable and easy to understand.
- Greatly reduces boilerplate. Most apps should only need a
componentsdirectory and aprovidersdirectory. - No need for
react-redux's<Provider>component. The higher-order component generated by@providehandles the contextual functionality (among a few other things!) for you at any level. - Automatic stores, multiple stores, and/or combined stores.
- Looks good in
react-devtools!
- You tell me!
npm install react-redux-provide --save
The API surface area is naturally tiny. You typically only need to concern yourself with the main export and occasionally a few utilities:
provide- The decorator that passes the providers'props(state) andactionsfromreduxto your components. It returns a higher-order component wrapped around the decorated component and accepts the following 3propswhich you can pass to any@provided component at any level to render anycontextas necessary:
-
providers- Object containing all the providers you want your components to use. The keys are the providers' names. -
providedState- Optional object containing the combined state of each provider'sreducers. The decorator will automatically separate the keys into their providers' stores as necessary. (This was originally calledinitialState, butprovidedStateis semantically better, especially for server rendering.) -
combinedProviders- Optional object or array of objects containing providers that share the same store(s). Seetest/providers/someCombinedProvider.jsfor an example of a provider (someCombinedProvider) that depends on another provider (list). Then rendering the app typically looks like this:import React from 'react'; import ReactDOM from 'react-dom'; import ReactDOMServer from 'react-dom/server'; import { App } from './components/index'; import providers, { list, someCombinedProvider } from './providers/index'; const context = { providers, combinedProviders: [ { list, someCombinedProvider } ], providedState: { list: [ 'easy!', 'right?' ] } }; ReactDOM.render(<App { ...context } />, document.getElementById('root')); // or server rendering export function renderToString(context) { return ReactDOMServer.renderToString(<App { ...context } />); }
-
addMiddleware (Object providers, Array|Function middleware)- Adds middleware(s) to each provider. -
addEnhancer (Object providers, Array|Function enhancer)- Adds enhancer(s) to each provider. -
createProviderStore (Object provider, Optional Object providedState)- Creates and returns a store specifically for some provider. -
createCombinedStore (Object providers, Optional Object providedState)- Creates and returns a shared store based on the combination of each provider. Especially useful when a provider's state depends on another provider's actions.
-
Components can have multiple providers, but the provided
props(actionsandreducers) should be unique to each provider. -
When instantiating components and determining which providers are relevant to each component, it will automatically create a new store for you if you haven't explicitly included a
storekey within yourproviderobject. Said store is of course shared throughout the components. -
Specify all of your
propTypes! Theprovidedecorator filters out anypropsnot within yourpropTypes, which keeps things efficient and helps with avoiding unnecessary re-renders. Plus, it's good design!
Basically, create some component with only the view in mind, plus whatever props you'd expect to use for triggering actions. For this quick example, we know react-redux-provide-list provides a list prop and a pushItem function, so in our @provide decorator we'll make it clear that's what we want.
From examples/good-times/components/GoodTimes.js:
import React, { Component, PropTypes } from 'react';
import provide from 'react-redux-provide';
@provide
export default class GoodTimes extends Component {
static propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired,
pushItem: PropTypes.func.isRequired
};
addTime() {
this.props.pushItem({
time: Date.now()
});
}
render() {
return (
<div className="good-times">
{this.renderButton()}
{this.renderTimes()}
</div>
);
}
renderButton() {
const style = {
fontSize: '20px',
marginBottom: '20px'
};
return (
<input
type="button"
style={style}
value="Let the good times roll"
onClick={::this.addTime}
/>
);
}
renderTimes() {
return this.props.list.map(
item => (
<li key={item.time}>
{new Date(item.time).toString()}
</li>
)
);
}
}Then when rendering the app, all we need to do is pass the provider(s) and their state(s) to the component(s):
import { render } from 'react-dom';
import provideList from 'react-redux-provide-list';
import GoodStuff from './components/GoodStuff';
const list = provideList();
const context = {
providers: { list },
providedState: {
list: [
{ fruit: 'apple' },
{ fruit: 'banana' }
{ vegetable: 'carrot' }
]
}
};
render(<GoodStuff { ...context } />, document.getElementById('root'));A provider is just an object with a few properties. At its core, it's your usual redux actions and reducers, which you'll typically need at a bare minimum. There are a few other things you can optionally include:
-
name- Defaults to its corresponding key within theprovidersprop. This will show up inreact-devtools- e.g., if you providelistandmaptoSomeComponent, in your dev tools, you'll seeSomeComponentwrapped with another component calledProvideSomeComponent(list,map). -
middleware- Include whatever middleware is used by your provider. This can be either an array of middlewares or a single middleware. -
enhancer- Include whatever enhancer is used by your provider's store. This can be either an array of enhancers or a single enhancer. -
merge (stateProps, dispatchProps, parentProps)- This incredibly useful function should return an object, which typically adds, removes, or replaces certain provided properties based on whatever logic you deem necessary. For example, inreact-redux-provide-list, if the component has anindexprop passed to its parent and expects anitemprop from the provider, themergefunction will attempt to provide theitemat thatindexwithin thelistto the component. -
store- This is your typicalreduxstore. See the Caveats section above about automatically generated stores. -
mapState- Maps each reduced state to the providedprops. By default, it will map them all. It's unlikely that you'll ever actually need to include this. -
mapDispatch- It's unlikely that you'll need to include this as well. This defaults todispatch => bindActionCreators(actions, dispatch)or if it's an object, it will useredux'swrapActionCreators.
You'll probably notice that many providers have everything in a single file. It makes sense for most simple use-cases, but you can of course structure everything however you want since each provider is ultimately just a single object.
You don't have to use generic provider packages (e.g., list, map, etc.), as they only exist to help with really common use-cases. For most apps, it works best to create a providers directory with an index that exports a providers object containing each provider, then we can simply import the object and pass it to the root component of the app when rendering.
Protip: Avoid sharing constants. Typically, the only place you should (occasionally) share constants is within providers. See bloggur's entries provider for a good example of shared constants, as its state depends on actions within react-redux-provide-history. Your components should have no knowledge of the constants used within your actions and reducers, which leads to a maximum separation of concerns and is always the best design. Your components should care about only 2 things: what to render and which actions to call.
