Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Prettier to recipes/reducers folder
  • Loading branch information
CodinCat committed May 3, 2017
commit 90468b314a7de85b83fafca345fa928e3ff68bac
42 changes: 20 additions & 22 deletions docs/recipes/reducers/BasicReducerStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ First and foremost, it's important to understand that your entire application re
- The first time the reducer is called, the `state` value will be `undefined`. The reducer needs to handle this case by supplying a default state value before handling the incoming action.
- It needs to look at the previous state and the dispatched action, and determine what kind of work needs to be done
- Assuming actual changes need to occur, it needs to create new objects and arrays with the updated data and return those
- If no changes are needed, it should return the existing state as-is.
- If no changes are needed, it should return the existing state as-is.

The simplest possible approach to writing reducer logic is to put everything into a single function declaration, like this:

```js
function counter(state, action) {
if (typeof state === 'undefined') {
state = 0; // If state is undefined, initialize it with a default value
state = 0 // If state is undefined, initialize it with a default value
}

if (action.type === 'INCREMENT') {
return state + 1;
}
else if (action.type === 'DECREMENT') {
return state - 1;
}
else {
return state; // In case an action is passed in we don't understand
return state + 1
} else if (action.type === 'DECREMENT') {
return state - 1
} else {
return state // In case an action is passed in we don't understand
}
}
```
Expand All @@ -36,11 +34,11 @@ There are some simple tweaks that can be made to this reducer. First, repeated
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
return state + 1
case 'DECREMENT':
return state - 1;
return state - 1
default:
return state;
return state
}
}
```
Expand All @@ -51,7 +49,7 @@ This is the basic structure that a typical Redux reducer function uses.

Redux encourages you to think about your application in terms of the data you need to manage. The data at any given point in time is the "*state*" of your application, and the structure and organization of that state is typically referred to as its "*shape*". The shape of your state plays a major role in how you structure your reducer logic.

A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data. For example, a basic Todo app's state might look like:
A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data. For example, a basic Todo app's state might look like:

```js
{
Expand Down Expand Up @@ -86,13 +84,13 @@ A typical app's state shape might look roughly like:

```js
{
domainData1 : {},
domainData2 : {},
appState1 : {},
appState2 : {},
ui : {
uiState1 : {},
uiState2 : {},
}
domainData1: {},
domainData2: {},
appState1: {},
appState2: {},
ui: {
uiState1: {},
uiState2: {}
}
}
```
```
112 changes: 59 additions & 53 deletions docs/recipes/reducers/BeyondCombineReducers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,48 @@ Similarly, if `sliceReducerA` happens to need some data from `sliceReducerB`'s s

```js
function combinedReducer(state, action) {
switch(action.type) {
case "A_TYPICAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
b : sliceReducerB(state.b, action)
};
}
case "SOME_SPECIAL_ACTION" : {
return {
// specifically pass state.b as an additional argument
a : sliceReducerA(state.a, action, state.b),
b : sliceReducerB(state.b, action)
}
}
case "ANOTHER_SPECIAL_ACTION" : {
return {
a : sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b : sliceReducerB(state.b, action, state)
}
}
default: return state;
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
```

Another alternative to the "shared-slice updates" issue would be to simply put more data into the action. This is easily accomplished using thunk functions or a similar approach, per this example:

```js
function someSpecialActionCreator() {
return (dispatch, getState) => {
const state = getState();
const dataFromB = selectImportantDataFromB(state);
dispatch({
type : "SOME_SPECIAL_ACTION",
payload : {
dataFromB
}
});
}
return (dispatch, getState) => {
const state = getState()
const dataFromB = selectImportantDataFromB(state)

dispatch({
type: 'SOME_SPECIAL_ACTION',
payload: {
dataFromB
}
})
}
}
```

Expand All @@ -67,35 +68,36 @@ A third approach would be to use the reducer generated by `combineReducers` to h

```js
const combinedReducer = combineReducers({
a : sliceReducerA,
b : sliceReducerB
});
a: sliceReducerA,
b: sliceReducerB
})

function crossSliceReducer(state, action) {
switch(action.type) {
case "SOME_SPECIAL_ACTION" : {
return {
// specifically pass state.b as an additional argument
a : handleSpecialCaseForA(state.a, action, state.b),
b : sliceReducerB(state.b, action)
}
}
default : return state;
switch (action.type) {
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: handleSpecialCaseForA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
default:
return state
}
}

function rootReducer(state, action) {
const intermediateState = combinedReducer(state, action);
const finalState = crossSliceReducer(intermediateState, action);
return finalState;
const intermediateState = combinedReducer(state, action)
const finalState = crossSliceReducer(intermediateState, action)
return finalState
}
```

As it turns out, there's a useful utility called [reduce-reducers](https://github.com/acdlite/reduce-reducers) that can make that process easier. It simply takes multiple reducers and runs `reduce()` on them, passing the intermediate state values to the next reducer in line:

```js
// Same as the "manual" rootReducer above
const rootReducer = reduceReducers(combinedReducers, crossSliceReducer);
const rootReducer = reduceReducers(combinedReducers, crossSliceReducer)
```

Note that if you use `reduceReducers`, you should make sure that the first reducer in the list is able to define the initial state, since the later reducers will generally assume that the entire state already exists and not try to provide defaults.
Expand All @@ -106,11 +108,15 @@ Note that if you use `reduceReducers`, you should make sure that the first reduc
Again, it's important to understand that Redux reducers are _just_ functions. While `combineReducers` is useful, it's just one tool in the toolbox. Functions can contain conditional logic other than switch statements, functions can be composed to wrap each other, and functions can call other functions. Maybe you need one of your slice reducers to be able to reset its state, and to only respond to specific actions overall. You could do:

```js
const undoableFilteredSliceA = compose(undoReducer, filterReducer("ACTION_1", "ACTION_2"), sliceReducerA);
const undoableFilteredSliceA = compose(
undoReducer,
filterReducer('ACTION_1', 'ACTION_2'),
sliceReducerA
)
const rootReducer = combineReducers({
a : undoableFilteredSliceA,
b : normalSliceReducerB
});
a: undoableFilteredSliceA,
b: normalSliceReducerB
})
```

Note that `combineReducers` doesn't know or care that there's anything special about the reducer function that's responsible for managing `a`. We didn't need to modify `combineReducers` to specifically know how to undo things - we just built up the pieces we needed into a new composed function.
Expand Down
Loading