Three principles directly taken from the Redux doc:
- Single source of truth: the state of your whole application is stored in an object tree within a single store.
- State is read-only: the only way to change the state is to emit an action, an object describing what happened.
- Changes are made with pure functions: to specify how the state tree is transformed by actions, you write pure reducers.
We can reduce the boilerplate by using Redux Actions to dispatch Flux Standard Actions:
// An FSA to be interpreted by reducers:
{
type : 'ACTION_TYPE',
payload : {
myValue: true
}
}
import { createAction } from 'redux-actions'
export const UI_TOGGLE_MAIN_NAV = 'UI_TOGGLE_MAIN_NAV'
export const toggleMenu = createAction(UI_TOGGLE_MAIN_NAV)
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-component-that-provides-data'
import App from './containers/App'
render(
<Provider myData="Baguette">
<App />
</Provider>,
document.getElementById('root')
)
The <Provider>
component adds data to the app tree's context
(more about React context
here) - a sort of global object.
The most straightforward way is by declaring a contextTypes
property:
const MyComponent = ({ myProps }, context) => (
<div>{context.myData}</div>
)
MyComponent.contextTypes = {
myData: React.PropTypes.string
}
But this is unstable to use it directly:
- Any API change will break every context provider and consumer
- The context is a global variable in the scope of a single React subtree = components are tightly coupled
shouldComponentUpdate
will collide with it and prevent updates in some cases
Instead you should use a higher-order component which is basically a function that takes a component and returns a new component enhnaced with additional functionality:
import React, { Component } from 'react'
function Consume(ComposedComponent, name) {
return class Data extends Component {
// get myData from the Provider somehow and set it in this.props
render() {
return <ComposedComponent {...this.props} />
}
}
}
import { Consume } from 'react-component-that-provides-data'
const MyComponent = ({ myData }) => (
<div>{myData}</div>
)
MyComponent.propTypes = {
myData: PropTypes.string
}
const ConsumerComponent = Consume(MyComponent)
This has some advantages:
- Isolation: there is no risk of method or property collision in your class.
- Interoperability: it works with any React Component, no matter how it was defined.
- Maintainability: the wrapper component will only have one functionality, which makes it easier to reason about.
import React from 'react'
import { render } from 'react-dom'
import Root from './containers/Root'
import configureStore from './store/configureStore'
const store = configureStore()
render(
<Root store={store} />,
document.getElementById('root')
)
containers/Root.js
:
import React, { PropTypes } from 'react'
import { Provider } from 'react-redux'
import App from './App'
const Root = ({ store }) => (
<Provider store={store}>
<App />
</Provider>
)
Root.propTypes = {
store: PropTypes.object.isRequired
}
export default Root
components/MainNavButton.js
:
import React from 'react'
import { connect } from 'react-redux'
import { toggleMenu } from '../../actions'
const MainNavButton = ({ onToggleMenu }) => (
<button
className="MainNavButton"
onClick={onToggleMenu}
>
=
</button>
)
const mapDispatchToProps = (dispatch) => {
return {
onToggleMenu: () => dispatch(toggleMenu())
}
}
export default connect(
null,
mapDispatchToProps
)(MainNavButton)
containers/MainNav.js
:
import './style.css'
import React, { Component } from 'react'
import { connect } from 'react-redux'
class MainNav extends Component {
render() {
const { active } = this.props
return (
<nav className={`MainNav ${active ? 'active' : ''}`}>
I am the MainNav
</nav>
)
}
}
const mapStateToProps = (state) => ({
active: state.ui.mainNavOpen
})
export default connect(
mapStateToProps,
null
)(MainNav)
Next: 04 - Router