|
1 | 1 | {
|
2 | 2 | "info": {
|
3 | 3 | "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." |
5 | 5 | },
|
6 | 6 | "pages": [
|
7 | 7 | {
|
|
72 | 72 | ]
|
73 | 73 | },
|
74 | 74 | {
|
75 |
| - "description": "log your store to the console and have a look.", |
| 75 | + "description": "log your `store` to the console and have a look.", |
76 | 76 | "tests": [
|
77 | 77 | "02/04"
|
78 | 78 | ],
|
|
231 | 231 | "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 ```",
|
232 | 232 | "tasks": [
|
233 | 233 | {
|
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\"", |
235 | 235 | "tests": [
|
236 | 236 | "05/01"
|
237 | 237 | ],
|
238 | 238 | "actions": [
|
239 | 239 | "open('src/index.js')"
|
240 | 240 | ],
|
241 | 241 | "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 };`" |
243 | 245 | ]
|
244 | 246 | },
|
| 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": [ |
245 | 272 | {
|
246 | 273 | "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.",
|
247 | 274 | "tests": [
|
|
282 | 309 | "06/04"
|
283 | 310 | ],
|
284 | 311 | "hints": [
|
285 |
| - "Try this: ```const reducers = combineReducers({\n pokemon\n});\n```" |
| 312 | + "Try this: `const reducers = combineReducers({pokemon});`" |
286 | 313 | ]
|
287 | 314 | },
|
288 | 315 | {
|
|
297 | 324 | "06/06"
|
298 | 325 | ],
|
299 | 326 | "hints": [
|
300 |
| - "Like this:`const defaultPokemon = [{\n id: 1,\n name: 'Luvdisc',\n ...\n`", |
| 327 | + "Like this: `const defaultPokemon = [{ id: 1, name: 'Luvdisc', ...`", |
301 | 328 | "Default params work like this: `fn(param1 = defaultParam, param2)`",
|
302 | 329 | "Like this: `const pokemon = (state = defaultPokemon, action) => {`"
|
303 | 330 | ]
|
304 | 331 | },
|
305 | 332 | {
|
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\".", |
307 | 334 | "tests": [
|
308 | 335 | "06/07"
|
309 | 336 | ],
|
310 | 337 | "hints": [
|
311 | 338 | "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;`" |
314 | 340 | ]
|
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": [ |
316 | 349 | {
|
317 | 350 | "description": "create a folder in your base directory called \"pokemon\" and add a file inside called \"index.js\"",
|
318 | 351 | "tests": [
|
|
357 | 390 | ]
|
358 | 391 | }
|
359 | 392 | ],
|
| 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 | + ], |
360 | 531 | "onPageComplete": ""
|
361 | 532 | }
|
362 | 533 | ]
|
|
0 commit comments