The most common complaint about using Redux with React is how it makes you write a lot of boilerplate. We’ll see how Redux lets us choose how verbose we'd like our code to be, depending on personal style, team preferences, longer term maintainability, etc.
High Profile Call Girls Nagpur Meera Call 7001035870 Meet With Nagpur Escorts
Maintaining sanity in a large redux app
1. MAINTAINING SANITY IN A
LARGE REDUX APP
Nitish Kumar
@nitishk88
https://github.com/nitishkr8
8
2. CONTENT
Brief introduction to Redux
Structuring the workspace
Selectors and memoization
Leveraging a type system
Reducing Boilerplate by using helper utilities
4. STRUCTURING THE WORKSPACE
Structuring workspace and
organizing files is one of the
hardest jobs in Computer Science.
Structuring a react/redux app can
be very daunting at times
Some established approaches to
organize a react/redux application
•Function First
•Feature First
5. FUNCTION FIRST
Function-first means that your top-level directories are named after the purpose
of the files inside. So you have: containers, components, actions, reducers, etc.
Pros:
•Isolates React from Redux - If you want to change the state management library,
you know which folders you need to touch. If you change the view library, you
can keep your redux folders intact.
Cons:
•Not scalable - As the app grows and you add more features, you add files into
the same folders. So you end up with having to scroll inside a single folder to
find your file.
•Tight coupling - The problem is also about coupling the folders together. A
single flow through your app will probably require files from all folders.
6. FEATURE FIRST
Feature-first means that the top-level directories are named after the main
features of the app: product, cart, session.
Pros:
•Scalable - This approach scales much better, because each new feature
comes with a new folder.
Cons:
•No separation b/w React and Redux - Changing one of them on the long run
is a very tricky job.
•Non-featured files – We end up with a folder common or shared, because
you want to reuse code across many features in your app.
8. DUCKS
Specifies a set of rules to bundle redux artifacts into isolated
module that is self contained.
Rules
MUST export default a function called reducer()
MUST export its action creators as functions
MUST have action types in the form npm-module-or-
app/reducer/ACTION_TYPE
MAY export its action types as UPPER_SNAKE_CASE, if an
external reducer needs to listen for them, or if it is a published
reusable library
9. EXAMPLE
// widgets.js
// Actions
const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';
// Reducer
export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: return state;
}
}
// Action Creators
export function loadWidgets() {
return { type: LOAD };
}
export function createWidget(widget) {
return { type: CREATE, widget };
}
export function updateWidget(widget) {
return { type: UPDATE, widget };
}
export function removeWidget(widget) {
return { type: REMOVE, widget };
}
// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget () {
return dispatch => get('/widget').then(widget =>
dispatch(updateWidget(widget)))
}
11. SELECTORS
What?
Are functions that take Redux state (, ownProps) as an argument and
return some data to pass to the component.
Why?
Can be used to compute derived data, allowing Redux to store the minimal
possible state
Smaller and decoupled React components. Performing data
transformations in the component makes them more coupled to the Redux
state and less generic/reusable
Performance Improvement
12. THE CASE FOR MEMOIZED
SELECTORS
Whenever a reducer returns, all connects get activated and calculates
their mapStateToProps to determine if the component needs an update.
The calculation most of the times is pretty unnecessary. This is rather
unavoidable but can be made really fast by leveraging immutable
operations and memoized selectors.
Memoized selectors are efficient. A selector is not recomputed unless one
of its arguments changes.
Selectors can be composable. They can be used as input to other
selectors.
14. LEVERAGING FLOW
Flow is a static type checker for JavaScript. It works by analyzing your
source code, reading (and inferring) type information and making sure
there are no conflicts. It can also power a lot of IDE-like features, for
example auto-complete, jump-to-definition, etc.
Redux has three parts that could be typed:
Actions
State
Reducers
15. TYPING STATE
type State = {
users: Array<{
id: string,
name: string
}>,
activeUserID: string,
// ...
}
We can enforce immutability in Flow by
making every property effectively “read-
only” using “covariant” properties throughout
your state object. Flow will complain when
you try to write to any of these properties.
type State = {
+foo: string
};
let state: State = {
foo: "foo"
};
state.foo = "bar"; // Error!
16. TYPING ACTIONS
The base type for Redux actions is an object with a type property.
type Action = {
+type: string
}
We can define Action type to be as simple as
type Action = {
+type: string
payload: Object
}
We can do much better than using constants for action types. Let’s start by
defining all possible actions flowing through our app. We will use a union
type for this.
type Action = { type: 'LOGGED_IN', userName: string }
| { type: 'LOGGED_OUT' }
{ type: 'LOGGED_IN', userName: 'foo' } // valid
{ type: 'LOGGED_IN', login: 'foo' } // `login` is not `userName`
{ type: 'LOGGED_IN' } // no `userName`
{ type: 'TYPO_HERE' } // `type` doesn't exist
17. Having all possible actions typed like this is great, because it’s very clear
what can happen inside our app.
The awesome thing about Flow’s support for the tagged unions is that it can
narrow down the action type depending on your code control flow:
function user(state: State, action: Action): State {
if (action.type === 'LOGGED_IN') {
// In this `if` branch Flow narrowed down the type
// and it knows that action has `userName: string` field
return { isLoggedIn: true, name: action.userName };
}
}
18. TYPING ACTION CREATORS
To type the action creator, just add a return type `Action` disjoint union declared earlier
function logOut(): Action {
return { type: 'LOGGED_OUT' }
}
In case we are using custom middleware to dispatch a Promise of Action
type PromiseAction = Promise<Action>
async function logIn(login: string, pass: string): PromiseAction {
let response = await fetch(...);
// Do something...
return {
type: 'LOGGED_IN',
userName: login,
}}
19. TYPING THUNK ACTIONS
type Action =
| { type: "FOO", foo: number }
| { type: "BAR", bar: boolean };
type GetState = () => State;
type PromiseAction = Promise<Action>;
type ThunkAction = (dispatch: Dispatch, getState: GetState) =>
any;
type Dispatch = (action: Action | ThunkAction | PromiseAction
| Array<Action>) => any;
function foo(): ThunkAction {
return (dispatch, getState) => {
const baz = getState().baz
dispatch({ type: "BAR", bar: true })
doSomethingAsync(baz)
.then(value => {
dispatch({ type: "FOO", foo: value })
})
}
}
20. TYPING REDUCERS
Reducers take the state and actions that we’ve
typed and pulls them together for one method.
function reducer(state: State, action: Action): State {
// ...
}
We can also validate that every single type of action has been
handled by using the empty type in your default case.
type State = { +value: boolean };
type FooAction = { type: "FOO", foo: boolean };
type BarAction = { type: "BAR", bar: boolean };
type Action = FooAction | BarAction;
function reducer(state: State, action: Action): State {
switch (action.type) {
case "FOO": return { ...state, value: action.foo };
case "BAR": return { ...state, value: action.bar };
default:
(action: empty);
return state;
}
}
21. REDUCING BOILERPLATE
The most common complaint about Redux is how it makes you write a lot of
boilerplate. We’ll see how Redux lets us choose how verbose we'd like our
code to be, depending on personal style, team preferences, longer term
maintainability, etc.
We can write our utilities for generate the following
Action creators
Reducers
22. MAKING ACTION CREATORS
A function that generates an action creator
function createAction(type, ...argNames) {
return function (...args) {
let action = { type }
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]
})
return action
}}
23. function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
function editTodo(id, text) {
return {
type: 'EDIT_TODO',
id,
text
}
}
function
removeTodo(id) {
return {
type:
'REMOVE_TODO',
id
}
}
const addTodo = createAction ('ADD_TODO', 'text')
const editTodo = createAction ('EDIT_TODO', 'id', 'text')
const removeTodo = createAction ('REMOVE_TODO', 'id')
24. MAKING ASYNC ACTION CREATORS
function createThunkAction(actionType,
actionThunkCreator) {
function fn(...actionArgs) {
var thunk = actionThunkCreator(...actionArgs)
return async function(dispatch, getState) {
try {
let payload = await thunk(dispatch, getState)
dispatch({ type: actionType, payload })
} catch (error) {
dispatch({ type: actionType, payload: error, error:
true
})
throw error
}
}}
fn.toString = () => actionType
return fn}
const fetchUsersForTitle =
createThunkAction(‘FETCH_USERS’, jobTitle => {
let result
return async (dispatch, getState) => {
// fetch users
result = await UserApi.list(// ...})
return result
}
})
25. GENERATING REDUCERS
We can write a function that lets us express
reducers as an object mapping from action types
to handlers.
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
Then define our reducers like this
const todos = createReducer([], {
['ADD_TODO'](state, action) {
let text = action.text.trim()
return [...state, text]
},
[TOGGLE_TODO'](state, action) {
// toggle and return new state
}
})
26. UTILITY LIBRARIES
redux-act
An opinionated lib to create actions and reducers for Redux. The main goal is to use
actions themselves as references inside the reducers rather than string constants.
redux-actions
Utility belt for FSA-compliant actions in Redux. It provides helpers for both handling and
creating action(s)
flux-standard-action (FSA) – A human-friendly standard for Flux action objects