Redux in Next
Nordnet Academy
Agenda
- Redux Global Store
- How to disconnect Component tree structure from Store/state structure.
- Support multiple states of same component type.
- Redux Code splitting
- Bonus: Reselect
Redux Global Store
Redux
- State of the app
- Changes to state is
- requested by actions.
- applied by reducers.
- State is one big object
Redux continued
State should be
- normalized
- loosely coupled
with presentation
Action (-creators)
- Is global, needs to be unique
- Should be as specific as possible
- Must contain all relevant info
function toggleVisibility(id){
return {
type: 'MyWidget / Toggle Visibility',
widget: id,
};
}
Reducers
- Mapped to only handle its local state
const DEFAULT_STATE ={};
function visibilityReducer(state = DEFAULT_STATE, action) {
switch (action.type) {
case 'MyWidget / Toggle Visibility':
return {
...state,
[action.widget]: !state[action.widget]
};
default:
return state;
}
}
function rootReducer = {
// Remember the key visible here.
visible: visibilityReducer,
};
Map*ToProps
Connects redux state with components
import connect from 'react-redux';
import toggleVisibility from './actions';
import visibilitySelector from './reducers';
const MyComponent = (props) => (
<div>
{ props.isVisible ? <span>VISIBLE!</span> : null }
<button onClick={props.toggle}>Toggle</button>
</div>
);
const mapStateToProps = (state, ownProps) => ({
// Still remember visible from before? This is it.
isVisible: state.visible[ownProps.widgetId],
});
const mapDispatchToProps = (dispatch, ownProps) => ({
toggle: () => dispatch(toggleVisibility(ownProps.widgetId));
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
// ....
const Outer = () =>(
<div>
<MyComponent widgetId="a" />
<MyComponent widgetId="b" />
</div>
);
Selectors
- Disconnects state and component.
- Takes one state and finds subparts.
- "Chain" them to go deep.
function visibilitySelector(state) {
// Still remembering the key visible?
return state.visible;
}
function isWidgetVisibleSelector(state, id) {
return visibilitySelector(state)[id] || false;
}
New MapStateToProps
Disconnecting components from state.
const mapStateToProps = (state, ownProps) => ({
// Getting rid of the visible key!
isVisible: isWidgetVisibleSelector(state, ownProps.widgetId),
});
STATE_NAMESPACE
const STATE_NAMESPACE = 'visible'; // Only reference to the word visible.
const DEFAULT_STATE ={};
function visibilityReducer(state = DEFAULT_STATE, action) {
// ...
}
export default function rootReducer = {
[STATE_NAMESPACE]: visibilityReducer,
};
// The above and below are coupled 1:1
function visibilitySelector(state) {
return state[STATE_NAMESPACE] || DEFAULT_STATE;
}
export function isWidgetVisibleSelector(state, id) {
return visibilitySelector(state)[id] || false;
}
Redux tip: export selectors with related reducers. They let you decouple views and action creators from state shape.
- Dan Abramov
Code splitting
Code splitting
- Reducers must be attached to the store
- Must use store.replaceReducer
- ReducerRegistry handles this
// route/MyComponent/index.js
// ...
const { reducer } = require('./MyComponent');
reducerRegistry.register(reducer);
// ...
Nested Widgets
- Nested widgets must expose reducers as a an object with a namespace.
- Parent widgets must import and re-export the reducers upwards.
// ChildComponent/index.js
import reducerA, {
STATE_NAMESPACE
} from './reducers';
const reducer = {
[STATE_NAMESPACE]: reducerA
};
export reducer;
// ParentComponent/index.js
import {
reducer as childReducer
} from 'ChildComponent';
import myReducer, {
STATE_NAMESPACE
} from './reducers';
const reducer = Object.assign({
[STATE_NAMESPACE]: myReducer,
}, childReducer);
export reducer;
Not the error you are looking for!
- We can only have 1 reducerRegistry
- Styleguideist have weird (app) lifecycles
- ReducerRegistry is a singleton which survives hot-reloads.
- The store.replaceReducer still works.
Can only set the listener for a ReducerRegistry once.
The End...
... Encore, encore, encore!
What is it good for?
Absolutely nothin' ?
Reselect
const state = {
accounts: [
{id: 'a', totalValue: 100},
{id: 'b', totalValue: 10},
{id: 'c', totalValue: 50},
],
selectedAccounts: [
'a',
'c'
]
}
import { createSelector } from 'reselect';
function getAllAccounts(state) {
return state.accounts;
}
function getSelectedIds(state) {
return state.selectedAccounts;
}
const getSelectedAccounts = createSelector(
[getAllAccounts, getSelectedIds],
(allAccounts, selectedIds) => {
return allAccounts.filter(
(acc) => selectedIds.includes(acc.id)
);
});
const selected = getSelectedAccounts(state);
console.log(selected);
/*
[
{id: 'a', totalValue: 100},
{id: 'c', totalValue: 50},
]
*/
Reselect continued
const state = {
accounts: [
{id: 'a', totalValue: 100},
{id: 'b', totalValue: 10},
{id: 'c', totalValue: 50},
],
selectedAccounts: [
'a',
'c'
]
}
import { createSelector } from 'reselect';
import { getSelectedAccounts } from 'selectors';
const getSelectedTotal = createSelector(
[getSelectedAccounts],
(selectedAccounts) => {
return selectedAccounts.reduce(
(result, acc) => result + acc.totalValue, 0)
}
);
const grandTotal = getSelectedTotal(state);
console.log(grandTotal); // 150
Redux in Next
By nordnetacademy
Redux in Next
- 791