Skip to content

Commit 3d894df

Browse files
committed
prepare tutorial outline
1 parent ddb0e55 commit 3d894df

File tree

8 files changed

+235
-36
lines changed

8 files changed

+235
-36
lines changed

README.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
A [CodeRoad](https://coderoad.github.io) tutorial for learning Redux.
44

5-
<!-- @import('08') -->
6-
<!-- @import('09') -->
7-
<!-- @import('10') -->
8-
95

106
## CodeRoad
117

@@ -101,3 +97,44 @@ const nextPokemon = state.pokemon.map(p => {
10197
pokemon: nextPokemon
10298
};
10399
```
100+
101+
##### Combine Reducers
102+
103+
Create modular, composable reducers with `combineReducers`.
104+
105+
Explanation here.
106+
107+
##### File Structure
108+
109+
Refactor your project into different files.
110+
111+
Explanation here
112+
113+
##### Logger
114+
115+
The power of middleware with "redux-logger".
116+
117+
Explanation here.
118+
119+
##### Second Action
120+
121+
Creating a "SORT_BY_POPULARITY" action.
122+
123+
```js
124+
function sortByVotes(a, b) {
125+
switch(true) {
126+
case a.votes < b.votes:
127+
return 1;
128+
case a.votes > b.votes:
129+
return -1;
130+
default:
131+
return 0;
132+
}
133+
}
134+
```
135+
136+
Sort pokemon by votes
137+
138+
##### Thunk
139+
140+
Using thunks for async actions.

coderoad.json

Lines changed: 181 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"info": {
33
"title": "CodeRoad Redux JS Tutorial",
4-
"description": "A [CodeRoad](https://coderoad.github.io) tutorial for learning Redux.\n\n<!-- @import('08') -->\n<!-- @import('09') -->\n<!-- @import('10') -->"
4+
"description": "A [CodeRoad](https://coderoad.github.io) tutorial for learning Redux."
55
},
66
"pages": [
77
{
@@ -72,7 +72,7 @@
7272
]
7373
},
7474
{
75-
"description": "log your store to the console and have a look.",
75+
"description": "log your `store` to the console and have a look.",
7676
"tests": [
7777
"02/04"
7878
],
@@ -231,17 +231,44 @@
231231
"description": "Reducers must be pure functions\n\nState is \"read only\".\n\nNotes\n```js\nconst nextPokemon = state.pokemon.map(p => {\n if (id === p.id) {\n p.votes += 1;\n }\n return p;\n });\n return {\n pokemon: nextPokemon\n };\n ```",
232232
"tasks": [
233233
{
234-
"description": "Time to make the VOTE_UP action change the state. Return a new list of Pokemon after incrementing \"votes\" of the pokemon with the matching \"id\"\nCreate modular, composable reducers with `combineReducers`.",
234+
"description": "Time to make the VOTE_UP action change the state. Return a new list of Pokemon after incrementing \"votes\" of the pokemon with the matching \"id\"",
235235
"tests": [
236236
"05/01"
237237
],
238238
"actions": [
239239
"open('src/index.js')"
240240
],
241241
"hints": [
242-
"'Try this: `case VOTE_UP: const pokemon = state.pokemon.map(p => {`')@hint('If the pokemon.id matches the payload.id, increase the votes by one')\n@hint('Don't forget to return the new state')\n@hint('Try returning `return { pokemon };`')\n\n+ Let's make a test to see that we are truly returning a new state. Call `Object.freeze()` on your `initialState`. `freeze` makes an object immutable - meaning the object can not be changed. And yet your reducer should still work, since it returns a new state each call.\n@test('05/02')\n@hint('Try this: `const initialState = Object.freeze({ ... })`')\n\n+ What if we were dealing with multiple keys on the state. We'd have to ensure that our changes return a complete new state each time. Use `Object.assign`\n@test('05/03')\n@hint('return `Object.assign({}, state, { pokemon: nextPokemon });`')\n\n\n## Combine Reducer"
242+
"If the pokemon.id matches the payload.id, increase the votes by one",
243+
"Don't forget to return the new state",
244+
"Try returning `return { pokemon };`"
243245
]
244246
},
247+
{
248+
"description": "Let's make a test to see that we are truly returning a new state. Call `Object.freeze()` on your `initialState`. `freeze` makes an object immutable - meaning the object can not be changed. And yet your reducer should still work, since it returns a new state each call.",
249+
"tests": [
250+
"05/02"
251+
],
252+
"hints": [
253+
"Try this: `const initialState = Object.freeze({ ... })`"
254+
]
255+
},
256+
{
257+
"description": "What if we were dealing with multiple keys on the state. We'd have to ensure that our changes return a complete new state each time. Use `Object.assign`",
258+
"tests": [
259+
"05/03"
260+
],
261+
"hints": [
262+
"return `Object.assign({}, state, { pokemon: nextPokemon });`"
263+
]
264+
}
265+
],
266+
"onPageComplete": "Now that you have an idea of how reducers work. Next we can look at how to create multiple, modular reducers."
267+
},
268+
{
269+
"title": "Combine Reducers",
270+
"description": "Create modular, composable reducers with `combineReducers`.\n\nExplanation here.",
271+
"tasks": [
245272
{
246273
"description": "create a new `const reducers` and set it equal to \"reducer\". Pass \"reducers\" into your store for now, instead of \"reducer\". We'll use combineReducers shortly, but let's not break the app yet.",
247274
"tests": [
@@ -282,7 +309,7 @@
282309
"06/04"
283310
],
284311
"hints": [
285-
"Try this: ```const reducers = combineReducers({\n pokemon\n});\n```"
312+
"Try this: `const reducers = combineReducers({pokemon});`"
286313
]
287314
},
288315
{
@@ -297,22 +324,28 @@
297324
"06/06"
298325
],
299326
"hints": [
300-
"Like this:`const defaultPokemon = [{\n id: 1,\n name: 'Luvdisc',\n ...\n`",
327+
"Like this: `const defaultPokemon = [{ id: 1, name: 'Luvdisc', ...`",
301328
"Default params work like this: `fn(param1 = defaultParam, param2)`",
302329
"Like this: `const pokemon = (state = defaultPokemon, action) => {`"
303330
]
304331
},
305332
{
306-
"description": "We no longer pass the entire \"state\" inside of our reducers, only the slice of our state the reducer needs to know. Rename all references to \"state\" inside of your \"pokemon\" reducer to what it really is now: \"pokemon\".\nRefactor your project into different files.",
333+
"description": "We no longer pass the entire \"state\" inside of our reducers, only the slice of our state the reducer needs to know. Rename all references to \"state\" inside of your \"pokemon\" reducer to what it really is now: \"pokemon\".",
307334
"tests": [
308335
"06/07"
309336
],
310337
"hints": [
311338
"Change three references to \"pokemon\" in your pokemon reducer",
312-
"First: 'const pokemon = (pokemon = defaultPokemon, action) => {`",
313-
"'Second: `const nextPokemon = pokemon.map(p => {`')@hint('Third: `default: return pokemon;`')\n\n\n\n\n## File Structur"
339+
"Third: `default: return pokemon;`"
314340
]
315-
},
341+
}
342+
],
343+
"onPageComplete": "The state remains the same, but now our reducers are much more modular. In the next step, we will separate our code into it's own file"
344+
},
345+
{
346+
"title": "File Structure",
347+
"description": "Refactor your project into different files.\n\nExplanation here",
348+
"tasks": [
316349
{
317350
"description": "create a folder in your base directory called \"pokemon\" and add a file inside called \"index.js\"",
318351
"tests": [
@@ -357,6 +390,144 @@
357390
]
358391
}
359392
],
393+
"onPageComplete": "Page 7 complete..."
394+
},
395+
{
396+
"title": "Logger",
397+
"description": "The power of middleware with \"redux-logger\".\n\nExplanation here.",
398+
"tasks": [
399+
{
400+
"description": "import `applyMiddleware` in \"index.js\"",
401+
"tests": [
402+
"08/01"
403+
],
404+
"actions": [
405+
"open('src/index.js')"
406+
]
407+
},
408+
{
409+
"description": "set the second param in createStore to `applyMiddleware()`",
410+
"tests": [
411+
"08/02"
412+
]
413+
},
414+
{
415+
"description": "install \"redux-logger\" using npm",
416+
"tests": [
417+
"08/03"
418+
]
419+
},
420+
{
421+
"description": "create a \"logger\" as the result of `createLogger()`",
422+
"tests": [
423+
"08/04"
424+
]
425+
},
426+
{
427+
"description": "pass \"logger\" into `applyMiddleware()`",
428+
"tests": [
429+
"08/05"
430+
]
431+
}
432+
],
433+
"onPageComplete": "Look in the console"
434+
},
435+
{
436+
"title": "Second Action",
437+
"description": "Creating a \"SORT_BY_POPULARITY\" action.\n\n```js\nfunction sortByVotes(a, b) {\n switch(true) {\n case a.votes < b.votes:\n return 1;\n case a.votes > b.votes:\n return -1;\n default:\n return 0;\n }\n }\n```\n\nSort pokemon by votes",
438+
"tasks": [
439+
{
440+
"description": "create an action type for 'SORT_BY_POPULARITY'",
441+
"tests": [
442+
"09/01"
443+
],
444+
"actions": [
445+
"open('src/pokemon/index.js')"
446+
]
447+
},
448+
{
449+
"description": "create an action creator called 'sortByPopularity'",
450+
"tests": [
451+
"09/02"
452+
]
453+
},
454+
{
455+
"description": "dispatch a `sortByPopularity` action after the two voteUp dispatches",
456+
"tests": [
457+
"09/03"
458+
]
459+
},
460+
{
461+
"description": "add a `SORT_BY_POPULARITY` case that returns `pokemon.sort();`",
462+
"tests": [
463+
"09/04"
464+
]
465+
},
466+
{
467+
"description": "create a sortByVotes function and pass it into the pokemon.sort function",
468+
"tests": [
469+
"09/05"
470+
]
471+
},
472+
{
473+
"description": "Make a `sortByKey` function, which is more reusable, by wrapping it in a function that takes a key",
474+
"tests": [
475+
"09/06"
476+
]
477+
},
478+
{
479+
"description": "You've just created a **thunk** - a function that returns a function. Pass your function into the pokemon.sort() method and give it the key of 'votes'",
480+
"tests": [
481+
"09/07"
482+
]
483+
}
484+
],
485+
"onPageComplete": "In the next step, we'll look at using thunks to call async actions"
486+
},
487+
{
488+
"title": "Thunk",
489+
"description": "Using thunks for async actions.",
490+
"tasks": [
491+
{
492+
"description": "install \"redux-thunk\" as a dependency",
493+
"tests": [
494+
"10/01"
495+
],
496+
"actions": [
497+
"open('src/index.js')"
498+
]
499+
},
500+
{
501+
"description": "import thunk from \"redux-thunk\"",
502+
"tests": [
503+
"10/02"
504+
]
505+
},
506+
{
507+
"description": "add thunk to applyMiddleware. The logger should always go last",
508+
"tests": [
509+
"10/03"
510+
]
511+
},
512+
{
513+
"description": "change the voteUp action creator to return a thunk with the param of \"dispatch\"",
514+
"tests": [
515+
"10/04"
516+
]
517+
},
518+
{
519+
"description": "voteUp should dispatch VOTE_UP",
520+
"tests": [
521+
"10/05"
522+
]
523+
},
524+
{
525+
"description": "voteUp should dispatch sortByPopularity after each vote",
526+
"tests": [
527+
"10/06"
528+
]
529+
}
530+
],
360531
"onPageComplete": ""
361532
}
362533
]

tutorial/02/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const store = createStore(reducer, initialState);
1919
@hint('call store with a simple reducer, `const store = createStore(state => state)`')
2020
@test('02/03')
2121

22-
+ log your store to the console and have a look.
22+
+ log your `store` to the console and have a look.
2323
@test('02/04')
2424
@hint('`console.log(store)`')
2525

tutorial/05/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const nextPokemon = state.pokemon.map(p => {
1919
+ Time to make the VOTE_UP action change the state. Return a new list of Pokemon after incrementing "votes" of the pokemon with the matching "id"
2020
@test('05/01')
2121
@action(open('src/index.js'))
22-
@hint('Try this: `case VOTE_UP: const pokemon = state.pokemon.map(p => {`')
2322
@hint('If the pokemon.id matches the payload.id, increase the votes by one')
2423
@hint('Don't forget to return the new state')
2524
@hint('Try returning `return { pokemon };`')

tutorial/06/index.md

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Combine Reducers
22
Create modular, composable reducers with `combineReducers`.
33

4+
Explanation here.
5+
46
+ create a new `const reducers` and set it equal to "reducer". Pass "reducers" into your store for now, instead of "reducer". We'll use combineReducers shortly, but let's not break the app yet.
57
@test('06/01')
68
@action(open('src/index.js'))
@@ -20,35 +22,21 @@ Create modular, composable reducers with `combineReducers`.
2022

2123
+ combineReducers(), and pass in your reducer ({ pokemon })
2224
@test('06/04')
23-
@hint('Try this: ```
24-
const reducers = combineReducers({
25-
pokemon
26-
});
27-
```
28-
')
25+
@hint('Try this: `const reducers = combineReducers({pokemon});`')
2926

3027
+ We're going to shake things up now to make our reducers more composable. Set the initial state inside of your `createStore` to simply be an empty object (`{}`)
3128
@test('06/05')
3229

3330
+ Thanks to `combineReducers` we can now define the initial state inside of each reducer. Get rid of "initialState", but keep the "pokemon" key and call it "defaultPokemon". It should be an array with three pokemon. Finally, pass the `defaultPokemon` as the default state in the pokemon reducer. You can use ES6 default params.
3431
@test('06/06')
35-
@hint('Like this:
36-
`const defaultPokemon = [{
37-
id: 1,
38-
name: 'Luvdisc',
39-
...
40-
`')
32+
@hint('Like this: `const defaultPokemon = [{ id: 1, name: 'Luvdisc', ...`')
4133
@hint('Default params work like this: `fn(param1 = defaultParam, param2)`')
4234
@hint('Like this: `const pokemon = (state = defaultPokemon, action) => {`')
4335

4436

4537
+ We no longer pass the entire "state" inside of our reducers, only the slice of our state the reducer needs to know. Rename all references to "state" inside of your "pokemon" reducer to what it really is now: "pokemon".
4638
@test('06/07')
4739
@hint('Change three references to "pokemon" in your pokemon reducer')
48-
@hint('First: 'const pokemon = (pokemon = defaultPokemon, action) => {`')
49-
@hint('Second: `const nextPokemon = pokemon.map(p => {`')
5040
@hint('Third: `default: return pokemon;`')
5141

52-
53-
5442
@onPageComplete('The state remains the same, but now our reducers are much more modular. In the next step, we will separate our code into it's own file')

tutorial/07/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## File Structure
22
Refactor your project into different files.
33

4+
Explanation here
5+
46
+ create a folder in your base directory called "pokemon" and add a file inside called "index.js"
57
@test('07/01')
68
@hint('create "src/pokemon/index.js"')
@@ -21,4 +23,4 @@ Refactor your project into different files.
2123
@test('07/05')
2224
@hint('Try this: `import { default as pokemon, voteUp } from './pokemon';`')
2325

24-
@onPageComplete('')
26+
@onPageComplete('Page 7 complete...')

tutorial/08/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## Logger
22
The power of middleware with "redux-logger".
33

4+
Explanation here.
5+
46
+ import `applyMiddleware` in "index.js"
57
@test('08/01')
68
@action(open('src/index.js'))

0 commit comments

Comments
 (0)