Entity State vs View State
Entity State is state fetched from a data source while View State is state the component state from the UI layer
React setState()
setState is an asynchronous method call react uses to batch all the state calls. It comes in 2 forms. setState(object)
and setState((prevState, props) => ({ ... }))
. Because the former merges objects shallowly it can cause bugs in your state management. Follow these rules when picking which form to use:
- use
setState(()=>{})
when there is a dependency from a previous property - use
setState({})
when there is no previous property dependency - when in doubt, use
setState(() => {})
because it's a pure function
Redux: View -> Action -> Reducer -> Store -> View
- An action is an Object with a
type
and optional payload. They are the ONLY way to change the state. It is dispatched and handled directly by the store. - Executing an action is called dispatching
- A Reducer is a pure function with 2 inputs - global state and action
- The Store holds the global state object
In Redux, the View dispatches an Action. The Action passes through Reducers. If the Action type
is applicable to the Reducer, it creates a new state else it returns the previous state. The Store then saves the new state for another View to subscribe from.
"Redux is only a state container. The state can be altered by using actions. The reducer takes care of the action. It uses the action and the old state to create a new state in the Redux store."
Advanced Redux
- Action Creators are functions that return actions. Instead of simply returning an Action as such
store.dispatch({ type: 'Test', myProp: 'foo'})
, you havestore.dispatch(myActionCreatorFn(args))
. It can give you additional control on how to manipulate the action you are returning and abstracts the process of structuring your returned action. combineReducer({...})
allows you to group multiple reducers by domain without having to worry about the structure of the intermediate reducer statereact-redux
wires React with Reactredux-actions
removes the boilerplate in creating actionsredux-promise
is a middleware that can returnasync
functionsredux-persist
for saving state
React Redux:
Redux middleware
A redux middleware is a function that takes the getState
and dispatch
functions. It allows you to execute arbitrary code before passing through your reducers. Set your middleware during store creation using Redux's applyMiddleware()
function.
A nuance between dispatch(action)
and next(action)
is that calling dispatch
will re-run the middleware chain from the very first, while next
will move the middleware chain down. Eventually, both will end up hitting your reducers. It is common practice to use dispatch
and next
in a middleware.
// logging middleware
/* Non ES6 style
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
*/
const logMiddleware => ({ getState, dispatch }) => next => action => {
console.log('Before reducers have run');
// pass along the action to the "next" reducer in the chain
const result = next(action);
console.log('After reducers have run');
return result;
};
// measuring middleware
const measureMiddleware = () => next => action => {
console.time(action.type);
const result = next(action);
console.timeEnd(action.type);
return result;
}
Redux-thunk for asynchronous actions
Redux-thunk is a middleware allowing you to dispatch actions that are functions. It works by checking the type of action dispatched. If it is a function, redux-thunk will execute it. It also has access to the state which you can use to validate before dispatching an action. Redux saga as an alternative using generators.
Sample thunk
function addRecipe(title) {
return function (dispatch, getState) {
const trimmedTitle = title.trim();
// if it's a duplicate, don't dispatch an action
const dupe = getState().recipes.find(r => r.title === trimmedTitle);
if (dupe) {
return;
}
dispatch({ type: 'ADD_RECIPE_STARTED' });
setTimeout(
() => dispatch({ type: 'ADD_RECIPE', title: trimmedTitle }),
1000
);
}
}
Best Practices
- Keep the action payload to a minimum
- Make sure to always unsubscribe your view
- Do not make the store globally available for performance reasons
- Follow Flux Standard Action for writing actions
- Consider placing validation logic in action creators or the middleware before passing it down to reducers. Having it in one of these places widens the scope of components that can recognize the change.
- Structure your reducers to mirror your applications state tree. It will make your files easy to spot for changes.
- To make sate more maintainable, have a reducer for every key in the state
- Organizing your files
-app
--todo
---TodoList.js
---TodoItem.js
---reducer.js
---actionCreators.js
---selectors.js
--filter
---Filter.js
---reducer.js
---actionCreators.js
---selectors.js
--notification
---Notifications.js
---reducer.js
---actionCreators.js
---selectors.js
--store
---store.js
Recipes
- Use
replaceReducer
to differentiate between logged in and logged out reducers - Use store enhancers to debug stores, rehydrate on load, save to local storage on every action.
applyMiddleware
is the only store enhancer that comes out of the box. Collection of enhancers