...or why getting in the React/Redux
mindset may mean not using either
React (and the ecosystem the grew up around it) shook up the front-end development world by unceremoniously killing several sacred front-end cows.
What we got for trading in these long held sacraments was an paradigm that was dramatically simpler to reason about and work in.
React uses an architectural paradigm called flux that featured unidirectional data flow (good) where each component connects to it's own store to get data (less good).
Redux comes along and using patterns, paradigms and architectures borrowed from functional languages, tweaks the standard flux model to "reduce" everything to a single, immutable store. Sanity is restored, complexity is diminished and peace reigns supreme again.
What goes in the store?
What doesn't go in the store?
Store Structure
Network requests
Network requests
99 times out of 100 I've found a simple thunk to be completely adequate for orchestrating network requests
File structure
Dogmatically avoid dogma
Avoid falling in line too quickly
Use a tool only when you need the tool
onEvent(actionCreator())
reducer(state, action) => newStateSlice
compose(reducers)
new state
store.getState()
connect()
store.dispatch(action)
combineReducers
The `reducer` is pivotal to what Redux is.
const reducer = (newThingSoFar, currentData) => ({
...newThingSoFar,
[currentData.key]: currentData.value
})
const someNewThing = someArray.reduce(reducer, {})
const reducer = (stateSlice, action) => ({
...stateSlice,
[action.type]: action.value
})
const newState = store.reduce(reducer, appState)
const reducer = (stateSlice, {type, ...action}) => {
switch (type) {
case 'RECIEVE_DATA':
return { ... stateSlice, ...action.data }
default:
return stateSlice
}
Think `[].reduce` callback
A Redux reducer takes a slice of the current state, and an action.
It returns a NEW slice of state depending on the action.type
The code branching based on action.type is conventionally done with a `switch` statement
It's best to make your action types exported constants.
export const RECIEVE_DATA = 'RECIEVE_DATA'
export default (stateSlice, {type, ...action}) => {
switch (type) {
case RECIEVE_DATA:
return { ... stateSlice, ...action.data }
default:
return stateSlice
}
Mostly this prevents typos, and provides a documented api for how to change your state.
The store is informed of desired changes with `actions`
import { ACTION_TYPE_NAME } from './reducers.js'
export default = ({
type: ACTION_TYPE_NAME,
...payload
})
Actions are plain objects with a `type` property. Everything else is up to you.
Typically you will want to use action creators to keep actions of the same type consistent
import { ACTION_TYPE_NAME } from './reducers.js'
export default = (payload) => ({
type: ACTION_TYPE_NAME,
...payload
})
Action creators are where Async actions occur, with the help of redux-thunk
import { ACTION_TYPE_NAME, SUCCESS_ACTION, ERROR_ACTION } from './reducers.js'
export default = (payload) => (dispatch) => {
dispatch({ type: ACTION_TYPE_NAME })
return fetch('/api/endpoint')
.then((result) =>
dispatch({ type: SUCCESS_ACTION, ...result}))
.catch((error) =>
dispatch({ type: ERROR_ACTION, ...error }))
}
Selectors are functions that know how to get the bit of state a components wants without the connected component needing to know the structure of the state itself
export const selectUserName = (appState) =>
appState.user.name
They typically live in your reducers file, along side your action types and reducer functions.
const MY_ACTION_TYPES = 'MY_ACTION_TYPES'
...
export default (state, action) => { /* reducer */}
...
const selectSomeState = (appState) => appState.path.to.thing
MapStateToProps
Higher Order Componets connect to the redux store and provide the proper parts of state as props to the target component
import { connect } from 'react-redux'
import MyComponent from './my-component.js'
import { getSomeBitOfState } from './reducers.js'
export default = connect(
mapStateToProps,
mapDispatchToProps,
customMerge,
)(MyComponent)
`connect` allows for us to map some bit of state to be passed as props to a target component
const mapStateToProps = (appState) => ({
someProp: appState.someState,
someOtherProp: appState.someOtherState
})
MapStateToProps
MapDispatchToProps allows us to pass functions that can dispatch actions to the Redux store as props to the target component
const mapDispatchToProps = (dispatch) => ({
onSubmit: (payload) => dispatch(onSubmitAction(payload))
})
MapDispatchToProps
customMerge allows us be more specific about how a components mapStateToProps, mapDispatchToProps, and own props objects are merged
const customMerge = (
stateProps,
dispatchProps,
ownProps
) => ({
...stateProps,
...dispatchProps,
...ownProps
})
CustomMerge