Skip to content

Commit b54d673

Browse files
committed
tutorial additions, add file structure step outline
1 parent 5474a52 commit b54d673

34 files changed

+526
-134
lines changed

README.md

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22

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

5-
<!-- @import('01') -->
6-
<!-- @import('02') -->
7-
<!-- @import('03') -->
8-
<!-- @import('04') -->
9-
<!-- @import('05') -->
10-
<!-- @import('07') -->
115
<!-- @import('08') -->
126
<!-- @import('09') -->
137

@@ -28,6 +22,85 @@ CodeRoad is an open-sourced interactive tutorial platform for the Atom Editor. L
2822

2923
## Outline
3024

25+
##### Project Setup
26+
27+
Getting a project setup is rarely easy. Luckily, we have a quick script that can do the work for us.
28+
29+
---
30+
31+
Running `> npm run setup` will do the following:
32+
33+
1. Install package dev dependencies
34+
2. Create an output directory called "dist"
35+
3. Install "concurrently" & "browser-sync" globally
36+
4. Run our app in the browser
37+
38+
You'll find this "setup" script located in your *package.json*.
39+
40+
41+
---
42+
43+
We'll be installing a lot of scripts from terminal. You may also want to consider installing the atom package ["platformio-ide-terminal"](https://github.com/platformio/platformio-atom-ide-terminal), which provides a terminal inside your editor.
44+
45+
##### The Store
46+
47+
The "single source of truth".
48+
49+
```js
50+
const store = createStore(reducer, initialState);
51+
```
52+
53+
##### Actions
54+
55+
Events that change the data.
56+
57+
##### 1. Actions
58+
```js
59+
const action = { type: 'ACTION_NAME' };
60+
```
61+
62+
##### 2. Action Creators
63+
64+
```js
65+
const actionName = () => ({ type: 'ACTION_NAME' });
66+
```
67+
68+
##### 3. Action Types
69+
70+
```js
71+
const ACTION_NAME = 'ACTION_NAME'
72+
```
73+
74+
##### Reducer
75+
76+
The data transformation
77+
78+
```js
79+
const reducer = (state) => {
80+
console.log(state);
81+
return state;
82+
};
83+
```
84+
85+
##### Pure Functions
86+
87+
Reducers must be pure functions
88+
89+
State is "read only".
90+
91+
Notes
92+
```js
93+
const nextPokemon = state.pokemon.map(p => {
94+
if (id === p.id) {
95+
p.votes += 1;
96+
}
97+
return p;
98+
});
99+
return {
100+
pokemon: nextPokemon
101+
};
102+
```
103+
31104
##### Combine Reducers
32105

33106
Create modular, composable reducers with `combineReducers`.

coderoad.json

Lines changed: 272 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,243 @@
11
{
22
"info": {
33
"title": "CodeRoad Redux JS Tutorial",
4-
"description": "A [CodeRoad](https://coderoad.github.io) tutorial for learning Redux.\n\n<!-- @import('01') -->\n<!-- @import('02') -->\n<!-- @import('03') -->\n<!-- @import('04') -->\n<!-- @import('05') -->\n<!-- @import('07') -->\n<!-- @import('08') -->\n<!-- @import('09') -->"
4+
"description": "A [CodeRoad](https://coderoad.github.io) tutorial for learning Redux.\n\n<!-- @import('08') -->\n<!-- @import('09') -->"
55
},
66
"pages": [
7+
{
8+
"title": "Project Setup",
9+
"description": "Getting a project setup is rarely easy. Luckily, we have a quick script that can do the work for us.\n\n---\n\nRunning `> npm run setup` will do the following:\n\n1. Install package dev dependencies\n2. Create an output directory called \"dist\"\n3. Install \"concurrently\" & \"browser-sync\" globally\n4. Run our app in the browser\n\nYou'll find this \"setup\" script located in your *package.json*.\n\n\n---\n\nWe'll be installing a lot of scripts from terminal. You may also want to consider installing the atom package [\"platformio-ide-terminal\"](https://github.com/platformio/platformio-atom-ide-terminal), which provides a terminal inside your editor.",
10+
"tasks": [
11+
{
12+
"description": "Open a terminal in this project directory and run `npm run setup`.",
13+
"tests": [
14+
"01/01"
15+
],
16+
"hints": [
17+
"Open a terminal in this project directory and run `npm run setup` to get setup"
18+
],
19+
"actions": [
20+
"open('package.json')",
21+
"set('{\n \"name\": \"coderoad-redux-js\",\n \"version\": \"0.1.0\",\n \"description\": \"Coderoad tutorial for using redux with raw javascript\",\n \"bugs\": {\n \"url\": \"https://github.com/shmck/coderoad-redux-js\"\n },\n \"license\": \"ISC\",\n \"author\": \"Shawn McKay <shawn.j.mckay@gmail.com>\",\n \"main\": \"index.js\",\n \"repository\": \"https://github.com/shmck/coderoad-redux-js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"dependencies\": {\n \"mocha-coderoad\": \"0.9.3\"\n },\n \"devDependencies\": {\n \"babel-preset-es2015\": \"^6.9.0\",\n \"babel-preset-react\": \"^6.11.1\",\n \"babelify\": \"^7.3.0\",\n \"browser-sync\": \"^2.13.0\",\n \"concurrently\": \"^2.2.0\",\n \"npm-watch\": \"^0.1.5\",\n \"coderoad-redux-js\": \"^0.1.0\"\n },\n \"watch\": {\n \"reload\": {\n \"patterns\": [\n \"src\"\n ],\n \"extensions\": \"js,jsx,css,html,scss\",\n \"ignore\": \"node_modules\",\n \"quiet\": false\n }\n },\n \"scripts\": {\n \"browserify\": \"browserify index.js --extension=.jsx -o dist/bundle.js -t [ babelify --presets [ es2015 react ] ]\",\n \"browsersync:reload\": \"browser-sync reload\",\n \"browsersync:start\": \"browser-sync start --server --files 'index.html dist/bundle.js'\",\n \"build\": \"npm run browserify\",\n \"reload\": \"npm run browserify && npm run browsersync:reload\",\n \"start\": \"concurrently --kill-others 'npm run build' 'npm run browsersync:start' 'npm run watch'\",\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n \"watch\": \"npm-watch\",\n \"setup\": \"npm install && mkdir -p dist && npm i -g concurrently browser-sync \"\n }\n}\n')"
22+
]
23+
},
24+
{
25+
"description": "Start the app by running `npm start`",
26+
"tests": [
27+
"01/02"
28+
],
29+
"actions": [
30+
"open('index.html')",
31+
"set('<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Worst Pokemon</title>\n <link\n rel=\"stylesheet\"\n href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\"\n crossorigin=\"anonymous\"\n />\n</head>\n<body>\n <div id=\"app\">\n <p>Check the console...</p>\n </div>\n\n <script src=\"dist/bundle.js\"></script>\n</body>\n</html>\n\n')"
32+
]
33+
}
34+
],
35+
"onPageComplete": "Continue to start working with Redux"
36+
},
37+
{
38+
"title": "The Store",
39+
"description": "The \"single source of truth\".\n\n```js\nconst store = createStore(reducer, initialState);\n```",
40+
"tasks": [
41+
{
42+
"description": "install Redux.",
43+
"hints": [
44+
"Run `npm install --save redux`."
45+
],
46+
"actions": [
47+
"open('index.js')"
48+
],
49+
"tests": [
50+
"02/01"
51+
]
52+
},
53+
{
54+
"description": "import `createStore` from the redux module.",
55+
"hints": [
56+
"Add `import { createStore } from 'redux';`"
57+
],
58+
"tests": [
59+
"02/02"
60+
]
61+
},
62+
{
63+
"description": "create your first store and call it `store`. Use a simple \"reducer\" function for now, let's say `state => state`.",
64+
"hints": [
65+
"declare your store, `const store`",
66+
"call store with a simple reducer, `const store = createStore(state => state)`"
67+
],
68+
"tests": [
69+
"02/03"
70+
]
71+
},
72+
{
73+
"description": "log your store to the console and have a look.",
74+
"tests": [
75+
"02/04"
76+
],
77+
"hints": [
78+
"console.log(store)"
79+
]
80+
},
81+
{
82+
"description": "log `store.getState()` to the console",
83+
"tests": [
84+
"02/05"
85+
],
86+
"hints": [
87+
"console.log(store.getState())"
88+
]
89+
},
90+
{
91+
"description": "move the initial state to the top of the file, and pass it in as a second param your `createStore`",
92+
"tests": [
93+
"02/06"
94+
],
95+
"hints": [
96+
"Move `initialState` above your `store`",
97+
"Pass in `initialState` as a second param to `createStore`"
98+
],
99+
"actions": [
100+
"insert('const initialState = {\n pokemon: [{\n id: 1,\n name: 'Luvdisc',\n description: 'This heart-shaped POKéMON earned its name by swimming after loving couples it spotted in the ocean’s waves.',\n votes: 3\n }, {\n id: 2,\n name: 'Trubbish',\n description: 'Wanting more garbage, they follow people who litter. They always belch poison gas.',\n votes: 2\n }, {\n id: 3,\n name: 'Stunfisk',\n description: 'Its skin is very hard, so it is unhurt even if stepped on by sumo wrestlers. It smiles when transmitting electricity.',\n votes: 0\n }]\n };\n')"
101+
]
102+
}
103+
],
104+
"onPageComplete": "As you can see, the store is just an object with various methods like \"dispatch\" and \"getState\". Let's see what these methods do in the next step."
105+
},
106+
{
107+
"title": "Actions",
108+
"description": "Events that change the data.\n\n##### 1. Actions\n```js\nconst action = { type: 'ACTION_NAME' };\n```\n\n##### 2. Action Creators\n\n```js\nconst actionName = () => ({ type: 'ACTION_NAME' });\n```\n\n##### 3. Action Types\n\n```js\nconst ACTION_NAME = 'ACTION_NAME'\n```",
109+
"tasks": [
110+
{
111+
"description": "create an action called `voteUp`",
112+
"tests": [
113+
"03/01"
114+
],
115+
"actions": [
116+
"open('index.js')"
117+
]
118+
},
119+
{
120+
"description": "change `voteUp` into an action creator.",
121+
"tests": [
122+
"03/02"
123+
],
124+
"hints": [
125+
"wrap your output object in round brackets",
126+
"Try this: `const voteUp = () => ({ type: \"VOTE_UP\" });`"
127+
]
128+
},
129+
{
130+
"description": "add a param of `id` to your action creator",
131+
"tests": [
132+
"03/03"
133+
]
134+
},
135+
{
136+
"description": "create an action type for `voteUp`",
137+
"hints": [
138+
"const VOTE_UP = \"VOTE_UP\""
139+
],
140+
"tests": [
141+
"03/04"
142+
]
143+
},
144+
{
145+
"description": "Use the action type inside your `voteUp` action creator. Notice how your editor may autocomplete the action type.",
146+
"tests": [
147+
"03/05"
148+
],
149+
"hints": [
150+
"change \"VOTE_UP\" to use the constant VOTE_UP",
151+
"Try this: `const voteUp = () => ({ type: VOTE_UP });`"
152+
]
153+
}
154+
],
155+
"onPageComplete": "Excellent! In the next unit we will look at how these actions can be used to transform data using a \"reducer\""
156+
},
157+
{
158+
"title": "Reducer",
159+
"description": "The data transformation\n\n```js\nconst reducer = (state) => {\n console.log(state);\n return state;\n};\n```",
160+
"tasks": [
161+
{
162+
"description": "Extract the `state => state` function in create store, and call in a new function called \"reducer\".",
163+
"tests": [
164+
"04/01"
165+
],
166+
"actions": [
167+
"open('index.js')"
168+
]
169+
},
170+
{
171+
"description": "Log the state inside of your reducer. What does it look like?",
172+
"tests": [
173+
"04/02"
174+
],
175+
"hints": [
176+
"Add a `console.log` statement inside of your reducer function",
177+
"Try this:```js\nconst reducer = (state) => {\n console.log(state);\n return state;\n};\n```"
178+
]
179+
},
180+
{
181+
"description": "Pass an action as a second param to the reducer",
182+
"tests": [
183+
"04/03"
184+
]
185+
},
186+
{
187+
"description": "Dispatch two voteUp actions through the reducer: `store.dispatch(voteUp(2))`. Change your log statement inside of your reducer to look like this: `console.log('state: ', state, 'action: ', action)`",
188+
"tests": [
189+
"04/04"
190+
]
191+
},
192+
{
193+
"description": "Create a `switch` statement and pass in `action.type`, the default return should return `state`",
194+
"tests": [
195+
"04/05"
196+
]
197+
},
198+
{
199+
"description": "The `switch` statement should have a `default` case that returns the state",
200+
"tests": [
201+
"04/06"
202+
]
203+
},
204+
{
205+
"description": "add a switch case for `VOTE_UP`. For now, just console.log the `id` of the action passed in and return the default state again. Tip: destructuring: `const { id } = action.payload;`",
206+
"tests": [
207+
"04/07"
208+
]
209+
}
210+
],
211+
"onPageComplete": "There are a few \"gotchas\" that come up with reducers. Reducers must be \"pure\" functions, let's find out how to accomplish this in the next step"
212+
},
213+
{
214+
"title": "Pure Functions",
215+
"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 ```",
216+
"tasks": [
217+
{
218+
"description": "Return a new list of Pokemon after incrementing \"votes\" of the pokemon with the matching \"id\"",
219+
"tests": [
220+
"05/01"
221+
]
222+
},
223+
{
224+
"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.",
225+
"tests": [
226+
"05/02"
227+
]
228+
},
229+
{
230+
"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`",
231+
"tests": [
232+
"05/03"
233+
],
234+
"hints": [
235+
"return `Object.assign({}, { pokemon: nextPokemon });`"
236+
]
237+
}
238+
],
239+
"onPageComplete": "Now that you have an idea of how reducers work. Next we can look at how to create multiple, modular reducers."
240+
},
7241
{
8242
"title": "Combine Reducers",
9243
"description": "Create modular, composable reducers with `combineReducers`.",
@@ -66,13 +300,48 @@
66300
]
67301
},
68302
{
69-
"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\".",
303+
"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\".\nUsing thunks for async actions.",
70304
"tests": [
71305
"06/07"
306+
],
307+
"hints": [
308+
"Change three references to \"pokemon\" in your pokemon reducer",
309+
"First: 'const pokemon = (pokemon = defaultPokemon, action) => {`",
310+
"'Second: `const nextPokemon = pokemon.map(p => {`')@hint('Third: `default: return pokemon;`')\n\n\n\n\n## Logge"
311+
]
312+
},
313+
{
314+
"description": "import `applyMiddleware`",
315+
"tests": [
316+
"07/01"
317+
]
318+
},
319+
{
320+
"description": "set the second param in createStore to `applyMiddleware()`",
321+
"tests": [
322+
"07/02"
323+
]
324+
},
325+
{
326+
"description": "install \"redux-logger\" using npm",
327+
"tests": [
328+
"07/03"
329+
]
330+
},
331+
{
332+
"description": "create a \"logger\" as the result of `createLogger()`",
333+
"tests": [
334+
"07/04"
335+
]
336+
},
337+
{
338+
"description": "pass \"logger\" into `applyMiddleware()`",
339+
"tests": [
340+
"07/05"
72341
]
73342
}
74343
],
75-
"onPageComplete": "The state remains the same as before, but now"
344+
"onPageComplete": "Look in the console"
76345
}
77346
]
78347
}

0 commit comments

Comments
 (0)