├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── egghead.io_video_tutorial_notes.md ├── index.html ├── package.json └── static-server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (https://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | _**Please read** our_ 2 | [**contribution guide**](https://github.com/dwyl/contributing) 3 | (_thank you_!) 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![learn-redux-header](https://cloud.githubusercontent.com/assets/194400/12328626/12f025de-bad4-11e5-9ebd-c0994b8f2f24.png) 2 | 3 | # Learn Redux 4 | 5 | Redux simplifies writing **well-structured**, ***predictable***, ***testable*** & ***maintainable*** JavaScript Web Applications. 6 | 7 | > **Note**: this guide is aimed at people who already have "***intermediate***" ***JavaScript experience***. 8 | > e.g. you have already built a couple of small apps without 9 | > using a framework and/or have used an older more complex library such as Angular, Ember, Backbone or Flux. 10 | > If you are just *starting* out on your web programming journey, 11 | > we *recommend* you checkout: 12 | > [https://github.com/dwyl/start-here#**javascript**](https://github.com/dwyl/start-here#javascript) 13 | > ***first*** 14 | and *then* come *back* here! 15 | > Bookmark/Star this GitHub repository so you don't forget where it is! 16 | 17 | 18 | ## Why? 19 | 20 | ![xkcd code quality](https://imgs.xkcd.com/comics/code_quality.png) 21 | 22 | JavaScript web applications can become messy if 23 | they don't have a *clear* organisation from the beginning. 24 | 25 | **Redux** is an ***elegant*** way 26 | to ***structure*** your **JavaScript web applications**. 27 | 28 | Having built *many* web applications over the past few years 29 | using *all* the most popular frameworks/libraries, we were *delighted* 30 | to discover Redux's *refreshingly simple approach*. 31 | 32 | While there is an ***initial learning curve*** 33 | we feel the *simplicity* 34 | of the *single* `store` (*snapshot of your app's state*) 35 | and applying changes to your app 36 | by *dispatching* succinct *functional* ***actions*** 37 | offers a ***significant*** 38 | **benefit** over other ways of organising your code. 39 | 40 | > *Please, don't take our word for it, 41 | skim through the notes we have made and* 42 | ***always decide for yourself***. 43 | 44 | ## What? 45 | 46 | Redux[1](https://github.com/dwyl/learn-redux/issues/22) *borrows the* ***reducer pattern*** *from* 47 | [***Elm*** Architecture](https://github.com/evancz/elm-architecture-tutorial/) 48 | which simplifies writing web apps. 49 | If you have *never heard of Elm*, ***don't worry***, 50 | you *don't need* to go read another doc before you can understand this... 51 | Just keep reading and (*hopefully*) everything will become clear. 52 | 53 | > _If **anything** is **unclear**, 54 | **please tell us** where you are stuck **so we can help**_! 55 | [![Join the chat at https://gitter.im/dwyl/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dwyl/chat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 56 | 57 | 58 | 59 | ### Three Principals 60 | 61 | Redux is based on three principals. 62 | see: https://redux.js.org/understanding/thinking-in-redux/three-principles 63 | 64 | #### 1. *Single* Source of Truth 65 | 66 | The state of your whole application is stored in a single object tree; the "Store". 67 | This makes it *much* easier to keep track of the "*State*" of your application 68 | at any time and roll back to any previous state. 69 | 70 | > If its not *intermediately* obvious why this is a good thing, 71 | we *urge* you to have faith and keep reading... 72 | 73 | #### 2. State is *Read-Only* ("*Immutable*") 74 | 75 | Instead of directly updating data in the store, we describe the update 76 | as a function which gets applied to the existing store and returns a new version. 77 | 78 | > See: https://en.wikipedia.org/wiki/Immutable_object 79 | 80 | #### 3. Changes are made Using *Pure Functions* 81 | 82 | To change the state tree we use "*actions*" called "*reducers*", 83 | these are simple functions which perform a *single* action. 84 | 85 | 86 |
87 | 88 | #### tl;dr 89 | 90 | Read more about JavaScript's Reduce (Array method): 91 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce 92 | and how to reduce an array of Objects: 93 | https://stackoverflow.com/questions/5732043/javascript-reduce-on-array-of-objects 94 | understanding these two things will help you grasp why Redux is so simple. 95 | 96 | You will see this abbreviated/codified as `(state, action) => state` 97 | to understand what this means, watch: [youtu.be/xsSnOQynTHs?t=15m51s](https://youtu.be/xsSnOQynTHs?t=15m51s) 98 | 99 | 100 | ## How? 101 | 102 | ### Learn Where Redux Got It's _Best_ Ideas From! 103 | 104 | [Redux](https://github.com/dwyl/learn-redux) "_takes cues from_" 105 | (_i.e. takes **all** it's **best ideas/features** from_) Elm. 106 | ![redux-borrows-elm](https://cloud.githubusercontent.com/assets/194400/25845941/c7a9ce78-34a7-11e7-91fb-a65f99ce0046.png)
107 | So... by learning **The Elm _Architecture_**, 108 | you will **_intrinsically_ understand Redux**
109 | which will help you learn/develop React apps.
110 | 111 | We wrote a ***complete beginner's step-by-step introduction*** 112 | to **The Elm _Architecture_** for **JavaScript** developers:
113 | [github.com/dwyl/**learn-elm-architecture**-in-**javascript**](https://github.com/dwyl/learn-elm-architecture-in-javascript) 114 | 115 | If _after_ you've learned Redux and built a couple of React Apps, 116 | you decide you want to discover where all the _best_ ideas 117 | in the React _ecosystem_ came from, 118 | checkout: [github.com/dwyl/**learn-elm**](https://github.com/dwyl/learn-elm) 119 | 120 | 121 | 122 | ### Learn from the _Creator_ of Redux! 123 | 124 | The *fastest* way to learn Redux is to watch the 125 | [Introductory Video Tutorials](https://egghead.io/series/getting-started-with-redux) 126 | recoded by **Dan Abramov** (*the Creator of Redux*).
127 | The videos are broken down into "bite size" chunks which are easily digestible. 128 | Total viewing time for the videos is [**66 minutes**]() 129 | 130 | We have made a set of ***comprehensive notes/transcriptions*** on the videos, these are in: 131 | [egghead.io_**video_tutorial**_***notes***.md](https://github.com/dwyl/learn-redux/blob/master/egghead.io_video_tutorial_notes.md) 132 | 133 | We _recommend_ keeping the **notes** open in a distinct window/browser 134 | while you are watching the videos; you can go a *lot* faster because all the sample code is included 135 | and if for any reason you do not _understand_ what Dan is saying you have the notes to refer to. 136 | 137 | ![learn-redux-video-notes-side-by-side](https://user-images.githubusercontent.com/194400/30913122-fa9cc800-a386-11e7-9801-228bcc5e6512.png) 138 | 139 | > *Please* give feedback and suggest improvements by creating issues on GitHub: 140 | https://github.com/dwyl/learn-redux/issues 141 | *Thanks*! 142 | 143 | 144 |
145 | 146 | ## Background Reading / Watching / Listening 147 | 148 | + GitHub Project: https://github.com/reduxjs/redux 149 | + Online Documentation: https://redux.js.org/ 150 | + ***Interview*** with [@gaearon](https://github.com/gaearon) (*Dan Abramov - creator of Redux*) 151 | on The **Changelog** Podcast: https://changelog.com/187 - 152 | Good history and insight into his motivations for learning to program 153 | and the journey that lead him to writing Redux. 154 | + Smart and Dumb Components by [Dan Abramov](https://github.com/gaearon) 155 | https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 156 | + Redux: Simplifying Application State in JavaScript - 157 | https://youtu.be/okdC5gcD-dM (*good overview by* [**Tim Griesser**](https://github.com/tgriesser) December 2015) 158 | + [Write Yourself A Redux](https://zapier.com/engineering/how-to-build-redux/) by Justin Deal/Zapier Engineering - Goes over the entire architecture and lets you build your own Redux _from scratch_! (April 27, 2017) 159 | + ***Ducks*** - a proposal for bundling reducers, action types and actions when using Redux - by [Erik Rasmussen](https://github.com/erikras) 160 | https://github.com/erikras/ducks-modular-redux 161 | + Redux ***best practices*** by [Will Becker](https://github.com/wbecker) https://medium.com/lexical-labs-engineering/redux-best-practices-64d59775802e 162 | + Starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform: https://github.com/erikras/react-redux-universal-hot-example 163 | + Full-Stack Redux Tutorial (Redux, React & Immutable.js) by 164 | [@teropa](https://github.com/teropa) 165 | https://teropa.info/blog/2015/09/10/full-stack-redux-tutorial.html - really good but takes 4h+! 166 | + Single source of truth: https://en.wikipedia.org/wiki/Single_Source_of_Truth 167 | + Redux Undo: https://github.com/omnidan/redux-undo 168 | + **Cartoon!** On Redux: https://code-cartoons.com/a-cartoon-intro-to-redux-3afb775501a6#.f9fhidgvl 169 | + ...And flux: https://code-cartoons.com/a-cartoon-guide-to-flux-6157355ab207#.jdauhkpjg 170 | + ...And time travel debugging (this one's short)! https://code-cartoons.com/hot-reloading-and-time-travel-debugging-what-are-they-3c8ed2812f35#.cvkq7d5du 171 | 172 | ### Architecture 173 | 174 | If you are building a React-based app 175 | you will most likely be using [react-router](https://github.com/rackt/react-router) 176 | to manage the routing of your client-side app ... 177 | React-Router manages an important piece of your application state: 178 | the URL. If you are using redux, you want your app state to fully 179 | represent your UI; if you snapshotted the app state, 180 | you should be able to load it up later and see the same thing. 181 | 182 | + Keep react-router and redux in sync: https://github.com/rackt/redux-simple-router 183 | + A Simple Way to Route with Redux (November 25, 2015) by [James Long @Mozilla](https://github.com/jlongster) 184 | https://jlongster.com/A-Simple-Way-to-Route-with-Redux 185 | 186 | ### Size (*Matters*) 187 | 188 | At the time of writing, the *minified* version of Redux is 189 | [**5.4kb**](https://github.com/dwyl/learn-redux/issues/11#issue-124671091) 190 | which is even *smaller* when GZipped! 191 | 192 | 193 | ## Frequently Asked Questions (*FAQ*) 194 | 195 | ### (Do I *Need* to use) React ? 196 | 197 | **Short Answer**: ***No***, Redux does not depend on or require you to use React; the two are separate and can be learned/used independently. 198 | 199 | **Longer Answer**: 200 | While *many* Redux apps and tutorials use React, Redux is ***totally separate*** from React. Dan's EggHead Video Tutorials do feature React heavily from **Lesson 8** *onwards*. 201 | 202 | React is a *good* fit for rendering views in a Redux-based app, however there are many other *great* alternative component-based virtual-DOM-enabled view rendering libraries (*#mouth-full*) that work *really* well with Redux; e.g: https://github.com/anthonyshort/deku 203 | 204 | Considering that React is *the* fastest growing "*view*" (*DOM Rendering*) library of 2015 205 | and the pace of its' adoption looks set to *continue* in 2016 206 | ... so it won't *hurt* you to know *how* to use React. 207 | 208 | We've made some notes to help you get started learning React: 209 | https://github.com/dwyl/learn-react 210 | 211 | You can/should use Redux to *organise* your application and ***optionally*** use React 212 | to `render` your views. 213 | 214 | ### (*Should I use*) Immutable.js ? 215 | 216 | **Short Answer**: ***Not Yet!*** 217 | 218 | **Longer Answer**: 219 | The *convention* in Redux apps is for the `state` to be 220 | [`immutable`](https://stackoverflow.com/questions/3200211/what-does-immutable-mean) 221 | this makes your app far more predictable because 222 | any/all changes to the `state` have to be done via an `action`. 223 | 224 | [Immutable.js](https://facebook.github.io/immutable-js/) 225 | makes the data structures in your application `state` 226 | more efficient (*in larger apps*) however, 227 | while you are learning Redux we suggest you ignore **immutable.js** 228 | as you will have more than enough to master for now. 229 | 230 | Once you have published your first app using Redux, 231 | come back to immutable.js to appreciate how it makes ***large apps*** 232 | run faster. As Lee Byron, the *creator* of Immutable.js states, 233 | for small apps without much change in `state`, adding Immutable.js 234 | will actually make your app perform *worse*! 235 | 236 | If you want to understand *why* using Immutable.js 237 | can be a ***good*** thing in ***large apps***, watch 238 | [Lee Byron's intro to Immutable](https://www.youtube.com/watch?v=kbnUIhsX2ds) 239 | 240 | 241 | ## Todo: [![pull requests welcomed!](https://img.shields.io/badge/pull%20requests-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/learn-redux/issues) 242 | 243 | + [ ] Explain why ***Unidirectional Data Flow*** is this "better" than bi-directional e.g: Angular.js 244 | 245 | ## Kudos to Fellow *DWYLers* 246 | 247 | > Props to [***Rafe***](https://github.com/rjmk) for telling us about Redux and Elm: https://github.com/rjmk/reducks *before* it was *cool* 248 | > Thanks to [***Milo***](https://github.com/bananaoomarang) for his 249 | *fantastic* demo/example: https://github.com/bananaoomarang/isomorphic-redux 250 | (*which he has painstakingly kept up-to-date with the latest Redux/React versions!*) 251 | > and *love* to [***Niki***](https://github.com/nikhilaravi) & 252 | > [***Jack***](https://github.com/jrans) for their 253 | > *enthusiasm* and *patience* while explaining it all to us ... 254 | 255 | ## *Thanks* for Learning with Us! 256 | 257 | If you found our notes useful, please share them with others by 258 | starring this repo and/or re-tweeting: 259 | 260 | [![dan_abramov_retweeted](https://cloud.githubusercontent.com/assets/194400/12523324/0ee0ac2c-c14e-11e5-9e6c-de4717fa474c.png)](https://twitter.com/dwylhq/status/687703493264732160) 261 | 262 | > https://twitter.com/dwylhq/status/687703493264732160 263 | -------------------------------------------------------------------------------- /egghead.io_video_tutorial_notes.md: -------------------------------------------------------------------------------- 1 | 2 | ## Notes for Dan Abramov's egghead.io Videos [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/nelsonic/learn-redux/issues) 3 | 4 | #### 1. The Single Immutable State Tree (*Principal #1*) 5 | 6 | > Video: https://egghead.io/lessons/javascript-redux-the-single-immutable-state-tree 7 | 8 | The **first principal** to learn in Redux is that you are going to represent your whole 9 | application ("State") as a single JavaScript Object. All changes and mutations 10 | to the state in Redux are *explicit* so it is possible to keep track of all of them. 11 | 12 | In this video Dan shows how the state of a Todo app changes over time as 13 | data is added and filters applied; its a *glimpse* of power of the single state tree. 14 | 15 |
16 | 17 | #### 2. Describing State Changes with Actions (*Principal #2*) 18 | 19 | > Video: https://egghead.io/lessons/javascript-redux-describing-state-changes-with-actions 20 | 21 | The **second principal** of Redux is that the **state tree** is ***read-only***; 22 | you cannot modify or write to it, instead, any time you want to change the state 23 | you need to dispatch an action. (i.e. *you can only "update" the state using a function*...) 24 | An action is a *plain Javascript Object* describing the change. 25 | Just like the state is the minimal representation of data in your app, 26 | the action is the minimal representation of the change to that data. 27 | The only requirement of an action is that it has a `type` property 28 | (*this more a description for your action*). The convention is to use a `String` 29 | because they are serializable (*i.e. easy to `JSON.stringify`*) 30 | 31 | > "*Any data that gets into your Redux Application gets there by actions*" 32 | 33 |
34 | 35 | #### 3. *Pure* and *Impure* Functions 36 | 37 | > Video: https://egghead.io/lessons/javascript-redux-pure-and-impure-functions 38 | 39 | Pure functions depend solely on the values of the arguments. 40 | Pure functions do not have any (*observable*) side-effects such as network 41 | or database calls. Pure functions just calculate the new value [of the state]. 42 | 43 | The functions you write in redux need to be pure. 44 | 45 | #### 4. The Reducer Function (*Principal #3*) 46 | 47 | > Video: https://egghead.io/lessons/javascript-redux-the-reducer-function 48 | 49 | The UI/View layer of an application is most predictable when it is described 50 | as a pure function of the application state. Pioneered by ~~React~~ [Ractive](https://github.com/ractivejs/ractive) and now adopted by several other 51 | frameworks, Redux compliments this approach with another idea: 52 | the state mutations in your app need to be described as a pure function 53 | that takes the previous state and the action being "dispatched" (*performed*) 54 | and returns the next state of your app. 55 | 56 | Inside any Redux app there is one function that takes the state of the whole 57 | application and the action being dispatched and returns the next state of 58 | the whole application. It is important that it does not modify the state given 59 | to it; it has to be pure, so it has to `return` a new `Object` 60 | Even in *large* applications there is still just a simple function 61 | that manages how the next state is calculated based on the previous state 62 | of the whole application and the action being dispatched. 63 | It does not have to be slow, for example: if I change the visibility filter 64 | I have to create the new object for the whole state, but I can keep the 65 | reference to the previous version of the Todo's array because the list of 66 | todos has not changed when we change the visibility filter; this is what makes 67 | Redux fast. 68 | 69 | > this is the 3rd and final principal of Redux: to describe state changes 70 | you have to write a function that takes the previous state of the app 71 | and the action being dispatched and returns the next state. 72 | The function has to be pure and is called the "Reducer". 73 | 74 |
75 | 76 | #### 5. Writing a Counter Reducer with Tests 77 | 78 | This video walks through creating a basic counter in Redux. 79 | 80 | > Video: https://egghead.io/lessons/javascript-redux-writing-a-counter-reducer-with-tests 81 | 82 | The first [*and only*] function in this video is the Reducer for the counter example. 83 | A reducer accepts state and action as arguments and returns the next state. 84 | 85 | Before writing any code, we write a few assertions (*tests*) using 86 | [**Michael Jackson**](https://github.com/mjackson)'s 87 | (*Yes, there's a* ***developer*** *with that name... and he's* ***really good***) 88 | ***Expect*** (testing/assertion) **library**: https://github.com/mjackson/expect 89 | 90 | We assert that when the state of the counter is zero and you pass an `INCREMENT` 91 | action, it should return 1. 92 | 93 | ```js 94 | expect ( 95 | counter(0, { type: 'INCREMENT' }) 96 | ).toEqual(1); 97 | ``` 98 | 99 | And similarly when the counter is 1 and we `INCREMENT` it should return 2. 100 | 101 | ```js 102 | expect ( 103 | counter(1, { type: 'INCREMENT' }) 104 | ).toEqual(2); 105 | 106 | // We add a test that check how `DECREMENT` works; from 2 to 1 and from 1 to zero: 107 | 108 | expect ( 109 | counter(2, { type: 'DECREMENT' }) 110 | ).toEqual(1); 111 | 112 | expect ( 113 | counter(1, { type: 'DECREMENT' }) 114 | ).toEqual(0); 115 | ``` 116 | 117 | If we run these tests [*in the browser*], they will fail because we have not 118 | even *begun* to implement the reducer. 119 | We are going to start by checking the action type. 120 | If the action type is `INCREMENT` we are going to `return state + 1` (*state plus one*) 121 | If the type is `DECREMENT` we are going to `return state - 1` (*state minus one*) 122 | 123 | ```js 124 | if (action.type === 'INCREMENT') { 125 | return state + 1; 126 | } else if (action.type === 'DECREMENT') { 127 | return state - 1; 128 | } 129 | ``` 130 | 131 | If you run the tests, you will find that that this is enough to get them to pass. 132 | > *Code for* 133 | [***Video 5 @ 1:15*** ](https://github.com/nelsonic/learn-redux/blob/8ded8853d5a789f94aff410eef0799bb66926a0d/index.html#L15) 134 | 135 | However, there are still some flaws in our implementation of the counter reducer. 136 | If we dispatch an action that it [*the reducer*] does not understand, 137 | it should return the current state of the application. 138 | 139 | ```js 140 | expect ( 141 | counter(1, { type: 'SOMETHING_ELSE' }) 142 | ).toEqual(1); 143 | ``` 144 | 145 | However if we check for that, we will see that this test fails 146 | because we currently don't handle unknown actions. 147 | So I'm going to add an `else` clause that returns the current state 148 | and the tests pass now. 149 | 150 | ```js 151 | if (action.type === 'INCREMENT') { 152 | return state + 1; 153 | } else if (action.type === 'DECREMENT') { 154 | return state - 1; 155 | } else { 156 | return state; 157 | } 158 | ``` 159 | 160 | And the tests pass now. 161 | 162 | > *Code for* 163 | [***Video 5 @ 1:49*** ](https://github.com/nelsonic/learn-redux/blob/d6c9051922e288583d5f43c45dbf3a57f1113648/index.html#L15) 164 | 165 | Another issue is that while the reducer is in control of the application state, 166 | *currently* it does not specify the initial state; in the case of the counter 167 | example that would be zero. 168 | 169 | The convention in Redux is that if the reducer receives `undefined` as the 170 | `state` argument, it *must* `return` what it considers to be the initial 171 | `state` of the application. In this case it will be zero. 172 | 173 | > *Code for* 174 | [***Video 5 @ 2:15*** ](https://github.com/nelsonic/learn-redux/blob/36775c88bb9d236f4918b1721c4d72c3ac8820a1/index.html#L18) 175 | 176 | "*Now come a few* ***cosmetic tweaks***" ... At the end of the video Dan replaces 177 | the `if/else` blocks with a `switch` statement* - which we *agree* is *neater* (*and works in* ***all browsers***) 178 | 179 | ```js 180 | switch (action.type) { 181 | case 'INCREMENT': 182 | return state + 1; 183 | case 'DECREMENT': 184 | return state - 1; 185 | default: 186 | return state; 187 | } 188 | ``` 189 | 190 | *However* Dan *also* makes a couple of changes which are *not* just "*cosmetic*": 191 | changing the reducer function to be an **ES6** 192 | [`Arrow Function`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) and also includes an **ES6** [`default parameter`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/default_parameters) 193 | syntax to specify what the state should be if its undefined. 194 | 195 | The reducer function written in ES5 (*Works in* ***ALL Browsers***): 196 | ```js 197 | function counter(state, action) { 198 | state = state || 0; // default parameter assignment before ES6 199 | /* reducer code here */ 200 | } 201 | ``` 202 | is re-written using ES6 features: (***Only Chrome*** *fully-supports both these new features*) 203 | 204 | ```js 205 | const counter = (state = 0, action) => { 206 | /* reducer code here */ 207 | } 208 | ``` 209 | 210 | ***Arrow functions*** can be fewer characters to type but are 211 | [***not supported***](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Browser_compatibility) 212 | in **Safari** *or* **Internet Explorer** 213 | [*at the time of writing*] ... 214 | ![ES6-arrow-functions-not-supported-in-safari-or-internet-explorer](https://cloud.githubusercontent.com/assets/194400/12050430/5800888c-aeed-11e5-91fb-0bb8ff2ae4a4.png) 215 | 216 | ***Default parameters*** are a *nice* addition to JavaScript (ECMAScript 2015) because 217 | they make it clear what the default value of the parameter should be if its unset, 218 | however they are 219 | [***not supported***](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/default_parameters#Browser_compatibility) in **Internet Explorer**, **Safari** *or* **Opera** [*at the time of writing*] ... 220 | ![es6-default_parameters-browser_compatibility](https://cloud.githubusercontent.com/assets/194400/12050412/095e590c-aeed-11e5-8dae-a8a4105715fb.png) 221 | 222 | These browsers still account for between 30%-50% of people using the internet in December 2015 223 | (*depending on the age/geography of the people using your app... 224 | see*: https://en.wikipedia.org/wiki/Usage_share_of_web_browsers ) 225 | And considering that *most* people take *ages* to upgrade to the latest browser 226 | Microsoft ***Internet Explorer 8*** *still has* 227 | [***10%*** *market share*!](https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0) 228 | and is [still available](https://www.microsoft.com/en-us/download/internet-explorer-8-details.aspx) 229 | to be downloaded. 230 | 231 | using ES6 features has two implications: 232 | + If you want to run the code in a browser you need Google Chrome ***Canary***. 233 | + And/Or, You need to "*transpile*" (*convert*) your code using ***Babel*** before running it in browsers. 234 | 235 | We will come back to Babel later... 236 | 237 |
238 | 239 | 240 | #### 6. Store Methods: getState(), dispatch(), and subscribe() 241 | 242 | > Video: https://egghead.io/lessons/javascript-redux-store-methods-getstate-dispatch-and-subscribe 243 | > Code: [Video 6 Code Snapshot]( https://github.com/nelsonic/learn-redux/blob/2430c6e95eacd61ebf7ff4a660cc64e80c9e883e/index.html) 244 | 245 | Video #6 picks up from where #5 finished, so if you skipped 246 | video 5, go back and watch it, and try writing/running the code! 247 | 248 | Dan starts off by showing how to include Redux (*from CDN JS*) 249 | in a client-side app so we can start using the methods. 250 | This is not the *recommended* way of loading Redux, but works fine for this 251 | example/demo. 252 | 253 | "*In real applications I suggest you use npm and a module bundler like 254 | webpack or browserify*". 255 | 256 | 257 | In this tutorial we are using a single function from Redux called `createStore`. 258 | 259 | Using **ES6** [**destructuring assignment**](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 260 | syntax to extract the `createStore` method from Redux: 261 | 262 | ```js 263 | const { createStore } = Redux; // 6 fewer characters to type. OMG! what will we do with all that extra free time?! 264 | // this is equivalent to: 265 | var createStore = Redux.createStore; 266 | ``` 267 | 268 | "The store binds together the **3 Principals** of Redux, 269 | it holds the current application state object, it lets you dispatch actions. 270 | When you create it [the store] you need to specify the reducer that tells 271 | how to update the state with actions. 272 | In this example we are calling `createStore` with `counter` as the reducer 273 | that manages the state updates." 274 | 275 | Redux Store has **3** (*important*) **methods**: 276 | 277 | + `getState` - retrieves the current state of the Redux store. In the case of our counter the initial state is Zero. 278 | + `dispatch` - lets you dispatch actions to change the state of your application. 279 | if we log the state of the application after dispatching an action (e.g: `INCREMENT`), we see that the state has changed to 1. 280 | (*the most commonly used method*) 281 | + `subscribe` - lets you register a callback that the Redux store will call 282 | any time an action has been dispatched. so you can update the UI of your application to reflect the current application state. 283 | 284 | The code at the end of video #6 looks like this: (*explanatory comments added*) 285 | 286 | ```js 287 | 288 | 289 | 290 | Learn Redux 291 | 292 | 293 | 294 | 295 | 327 | 328 | 329 | ``` 330 | 331 | Try viewing the [`index.html`](https://github.com/nelsonic/learn-redux/blob/2430c6e95eacd61ebf7ff4a660cc64e80c9e883e/index.html) file in [**Chrome** ***Canary***](https://github.com/nelsonic/learn-redux/issues/5#issue-123923845) 332 | 333 | > Download Chrome Canary: https://www.google.co.uk/chrome/browser/canary.html 334 | 335 | 336 | #### 7. Implementing Store from Scratch 337 | 338 | > Video: https://egghead.io/lessons/javascript-redux-implementing-store-from-scratch 339 | > Code: [Video #7 Code Snapshot](https://github.com/nelsonic/learn-redux/blob/17432aacb7e75702fe66338d9eacf27ffcca33c7/index.html#L15-L43) 340 | 341 | In the 7th Video Dan shows how the Redux store is *implemented* 342 | in ***20 lines of code***: 343 | 344 | ```js 345 | const createStore = (reducer) => { 346 | let state; 347 | let listeners = []; 348 | 349 | const getState = () => state; // return the current state (object) 350 | 351 | const dispatch = (action) => { 352 | state = reducer(state, action); 353 | listeners.forEach(listener => listener()); 354 | }; 355 | const subscribe = (listener) => { 356 | listeners.push(listener); 357 | return () => { // removing the listener from the array to unsubscribe listener 358 | listeners = listeners.filter(l => l !== listener); 359 | }; 360 | }; 361 | 362 | dispatch({}); 363 | 364 | return { getState, dispatch, subscribe }; 365 | } 366 | ``` 367 | 368 | "Because the subscribe function can be called many times, 369 | we need to keep track of all the changed listeners. 370 | And any time it is called we want to push the new listener into the (`listeners`) array. 371 | Dispatching an action is the only way to change the internal state. 372 | in order to calculate the new state we call the reducer with the state 373 | and the action being dispatched. 374 | And after the state is updated we need to notify every change listener by calling it. 375 | 1:44 - There is an important missing piece here: we have not provided a way 376 | to unsubscribe a listener. But instead of adding a dedicated `unsubscribe` method, 377 | we will just return a function from the subscribe method that removes this listener from the `listeners` array. 378 | 2:03 - Finally by the time the store is returned we want it to have the initial 379 | state populated. so we are going to dispatch a dummy action just to get the 380 | reducer to return the initial value. 381 | 2:18 - this implementation of the Redux store is (*apart from a few minor details 382 | and edge cases*) is the `createStore` we ship with Redux." 383 | 384 | > Once you have watched the video, checkout the source code for Redux.createStore 385 | on Github: https://github.com/rackt/redux/blob/master/src/createStore.js 386 | 387 |
388 | 389 | 390 | #### 8. React Counter Example 391 | 392 | > Video: https://egghead.io/lessons/javascript-redux-react-counter-example 393 | 394 | "In the simplest counter example I update the `document.body` *manually* 395 | any time the store state changes, but of course this approach does not scale 396 | to complex applications. So instead of manually updating the DOM I'm going 397 | to use React." 398 | 399 | I'm adding two scripts to the `` corresponding to React and [React-DOM](https://facebook.github.io/react/docs/glossary.html) 400 | and a `root` div to render to: 401 | 402 | ```js 403 | 404 | 405 | ``` 406 | > These scripts are available on CDNJS: https://cdnjs.com/libraries/react/ 407 | You can opt to use `fb.me` as your React CDN if you *prefer*. 408 | 409 | So now I can call `ReactDOM.render` with my root component. 410 | The render function is called any time the store state changes, 411 | so I can safely pass the sate of the store as a `prop` to my 412 | root component. 413 | 414 | ```js 415 | const Counter = ({ value }) => ( 416 |

{value}

417 | ); 418 | 419 | const render = () => { 420 | ReactDOM.render( 421 | , 422 | document.getElementById('root') 423 | ); 424 | }; 425 | ``` 426 | 427 | Since the state is held inside the Redux Store the counter component can 428 | be a simple function which is a supported way of declaring components 429 | since React version `0.14`. 430 | 431 | Now I want to add increment and decrement buttons to the component, 432 | but I don't want to *hard-code* the Redux dependency into the component, 433 | so I just add `onIncrement` and `onDecrement` props as callbacks. 434 | 435 | In my render method I pass the callbacks that call `store.dispatch` 436 | with appropriate actions. 437 | Now the application state is updated when I click the buttons. 438 | 439 | by the *end* of Video 8 your code should look like this: 440 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/a6cbc789e078d8c42a73066dbb94adf8bd5a7f3f/index.html#L19-L72) 441 | 442 | ##### *Recap* 443 | 444 | [1:20] Now let's recap how this application works. 445 | 446 | The `counter` component is what I call a "*dumb component*", 447 | it does not contain *any* business logic, it only specifies how the current application state transforms into renderable output 448 | and how the callback passed via props are bound to the event handlers. 449 | 450 | When we render a counter we specify that its `value` should be taken 451 | from the Redux `store` *current* state. 452 | And when the user presses `increment` [button] or `decrement` [button] 453 | we dispatch the corresponding action to the Redux store. 454 | 455 | Our reducer specifies how the *next* state is calculated based on the 456 | *current* state and the `action` being dispatched. 457 | 458 | And *finally* we subscribe to the Redux store so our `render` function 459 | runs any time the state changes, so the `counter` [component] 460 | gets the *current* state. 461 | 462 | 463 | 464 | #### Notes on using JSX Syntax in React in Browsers 465 | 466 | *Most* React.js Examples are written using 467 | [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) syntax. 468 | This is not *standard* JavaScript so no browser can *understand* it. 469 | 470 | If you want the Counter *example* to work in the browser (*without having to compile your counter component with babel*) you will need to include the `JSXTransformer`: 471 | 472 | ```js 473 | 474 | ``` 475 | **note**: in-browser compilation of JSX is [not recommended](https://reactjs.org/blog/2015/06/12/deprecating-jstransform-and-react-tools.html#other-deprecations) for "*Production*" use. 476 | instead you will need to *compile* your JSX to JS using Babel... 477 | 478 | For more detail, 479 | read: https://facebook.github.io/react/docs/tooling-integration.html#jsx 480 | 481 | Don't forget to add `type="text/jsx"` to your script tag in `index.html` 482 | to ensure that the JSX in the React Component is transformed. 483 | see: https://stackoverflow.com/questions/28100644/reactjs-uncaught-syntaxerror-unexpected-token 484 | 485 | > *Final version* of *working* code for Video 8: 486 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/a6cbc789e078d8c42a73066dbb94adf8bd5a7f3f/index.html#L19-L72) 487 | Run it by opening it in **Google Chrome Canary**: 488 | 489 | ![learn-redux-video-9-counter-example](https://cloud.githubusercontent.com/assets/194400/12079438/c0203cdc-b230-11e5-9338-00254dc9761a.png) 490 | 491 | 492 | #### 9. Avoiding Array Mutations with concat(), slice(), and ...spread 493 | 494 | In this video we learn how to avoid mutating arrays using concat(), slice(), and the ES6 array spread operator. 495 | 496 | > Video: https://egghead.io/lessons/javascript-redux-avoiding-array-mutations-with-concat-slice-and-spread 497 | 498 | "In this lesson I use the *expect* library to make assertions 499 | and [**deep-freeze**](https://github.com/substack/deep-freeze) to make sure my code is ***free*** *of* ***mutations***." 500 | 501 | ```js 502 | 503 | 504 | ``` 505 | These are loaded from [@Substack](https://github.com/substack)'s CDN: https://wzrd.in 506 | 507 | "Let's say that I want to implement a **counter** ***list*** application. 508 | I would need to write a few function that operate on its state and 509 | its state is an `Array` of JavaScript *Numbers* representing the individual counters." 510 | 511 | The first function I want to write is called addCounter 512 | and all it should do is to *append* a zero at the end 513 | of the passed `Array`. 514 | 515 | 516 | ```js 517 | const addCounter = (list) => { 518 | // write the tests first then implement the function to make them pass. 519 | }; 520 | 521 | const testAddCounter = () => { 522 | const listBefore = []; 523 | const listAfter = [0]; 524 | 525 | deepFreeze(listBefore); 526 | 527 | expect( 528 | addCounter(listBefore) 529 | ).toEqual(listAfter); 530 | } 531 | 532 | testAddCounter(); 533 | console.log('All tests passed.'); 534 | ``` 535 | 536 | At first I use the 537 | [`Array.push()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/push) 538 | method to add an item at the end of the `Array`, and it works. 539 | > Snapshot of code for [Video 9 @ 0:36](https://github.com/nelsonic/learn-redux/blob/65fd87d59a91ca1b12fb8b3a3d1e5516ee520174/index.html#L17-L20) 540 | 541 | *However* we need to learn to ***avoid mutations*** in Redux 542 | and I'm enforcing this by calling `deepFreeze` on the original array. 543 | Now my attempt to `.push` does not work; it cannot add a new property to a "frozen" object. 544 | Instead of `.push` I'm going to use the `concat` method which does not *modify* the array. 545 | 546 | ```js 547 | const addCounter = (list) => { 548 | return list.concat([0]); 549 | }; 550 | ``` 551 | 552 | Now the tests pass without *mutations*. 553 | 554 | And I can also use the new **ES6** [***spread operator***](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 555 | to write the code in a more concise way: 556 | 557 | ```js 558 | const addCounter = (list) => { 559 | return [...list, 0]; 560 | }; 561 | ``` 562 | 563 | > **Note**: *Again*, (at the time of writing) You will need to be running 564 | [**Chrome**](https://www.google.co.uk/chrome/browser/canary.html) or 565 | [**Firefox**](https://www.mozilla.org/en-GB/firefox/developer/) 566 | for this example to work because the 567 | [***spread operator***](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 568 | is still [***not*** *(yet)* ***supported***](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility) in *all* browsers ... even though it is a *Standard* ... 569 | 570 | 571 | The next function is `removeCounter` and it accepts two arguments: `list` (*an `Array` of `Numbers`*) and `index` the `Number` to *skip* from the `Array`. 572 | 573 | So if I've got three numbers 574 | and I'm passing 1 as the second argument, 575 | I expect to receive an `Array` with *two* items with the *second* item *skipped* in the `Array`: 576 | 577 | 578 | ```js 579 | const testRemoveCounter = () => { 580 | const listBefore = [0, 10, 20]; 581 | const listAfter = [0, 20]; 582 | expect( 583 | removeCounter(listBefore, 1) 584 | ).toEqual(listAfter); 585 | }; 586 | ``` 587 | 588 | *Usually* to `delete` an item from an `Array` I would use the `splice` method. 589 | However `splice` is a mutating method, so you can't use it in Redux. 590 | I'm going to `deepFreeze` the (`listBefore`) `Array` object, 591 | and now I need to figure out a *different* way of removing an item from 592 | the array *without mutating it*. 593 | I'm using a method called `slice` here and it does not have *anything* to do with `splice`; it is ***not mutating*** and it gives me a part of the `Array` from some beginning to some end index. 594 | So what I am doing is taking the parts before the index I want to skip 595 | and after the index I want to skip 596 | and I `concat` them to get a new array: 597 | 598 | ```js 599 | const removeCounter = (list, index) => { 600 | return list 601 | .slice(0, index) 602 | .concat(list.slice(index+1)); 603 | }; 604 | ``` 605 | 606 | Finally, instead of writing it as a method chain with `concat` calls, 607 | I can use the **ES6** [***spread operator***](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 608 | to write it more concisely: 609 | 610 | ```js 611 | const removeCounter = (list, index) => { 612 | return [ 613 | ...list.slice(0, index), 614 | ...list.slice(index + 1) 615 | ]; 616 | }; 617 | ``` 618 | 619 | > **Note**: *make* ***sure*** *you* ***understand*** *how* ***both*** 620 | *of these work before proceeding ... Dan is a big fan of his ES6; he uses it* ***everywhere***! 621 | 622 | Now that we have implemented adding and removing counters, 623 | lets implement *incrementing* the counter: 624 | 625 | ```js 626 | incrementCounter = (list, index) => { 627 | 628 | }; 629 | 630 | // write a test/assertion before implementing the function: 631 | testIncrementCounter = () => { 632 | const listBefore = [0, 10, 20]; 633 | const listAfter = [0, 11, 20]; 634 | 635 | expect( 636 | incrementCounter(listBefore, 1) 637 | ).toEqual(listAfter); 638 | 639 | } 640 | ``` 641 | 642 | The `incrementCounter` function takes two arguments: 643 | `list` - the `Array` (*of all our counters*) 644 | and `index` - the counter that should be incremented. 645 | So the returned value (`Array`) has the same count of items 646 | but one of them is incremented. 647 | 648 | Directly setting the value at the `index` *works* but this a mutation. 649 | so if we add a `deepFreeze` call its *not* going to work *anymore*. 650 | 651 | So *how* do we *replace* a single value in the array 652 | ***without mutating*** it? 653 | it turns out the *answer* is *really similar* to how we *remove* an item. 654 | 655 | ```js 656 | incrementCounter = (list, index) => { 657 | return list 658 | .slice(0, index) 659 | .concat(list[index] + 1) 660 | .concat(list.slice(index + 1)); 661 | }; 662 | ``` 663 | 664 | We want to take a slice *before* the `index` 665 | and `concat` it with a single item `Array` with a *new* value 666 | and then `concat` it with the *rest* of the original `Array`. 667 | 668 | Finally with the **ES6** [***spread operator***](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 669 | we can spread over the left part of the `Array` 670 | *specify* the *new* item, 671 | and then *spread* over the right part of the *original* `Array` 672 | and this *looks* much nicer ... 673 | 674 | ```js 675 | incrementCounter = (list, index) => { 676 | return [ 677 | ...list.slice(0, index), 678 | list[index] + 1, 679 | ...list.slice(index + 1) 680 | ]; 681 | }; 682 | ``` 683 | 684 | In this lesson you learned how to use the `concat` method 685 | *or* the *spread operator* 686 | and the `slice` method to add, remove and change items in arrays 687 | *without mutating* them 688 | and how to *protect* yourself from *mutation* with `deepFreeze` 689 | in your tests. 690 | 691 | 692 | > Code snapshot at the end of Video 9: 693 | [`index.html`(https://github.com/nelsonic/learn-redux/blob/f25da6293c26b9262c888219139830979c51633b/index.html#L16-L67) 694 | 695 |
696 | 697 | #### 10. Avoiding Object Mutations with Object.assign() and ...spread 698 | 699 | > Video: https://egghead.io/lessons/javascript-redux-avoiding-object-mutations-with-object-assign-and-spread 700 | 701 | Like in the previous example I use `expect` and `deepFreeze` libraries 702 | from NPM to make test assertions. 703 | And this time I'm testing a function called `toggleTodo` 704 | that takes a todo `Object` and *flips* its "*completed*" field. 705 | So if `completed` was `false` it should be `true` in the returned value 706 | or if it was `true` it should be `false` 707 | 708 | ```js 709 | const toggleTodo = (todo) => { 710 | 711 | }; 712 | 713 | const testToggleTodo = () => { 714 | const todoBefore = { 715 | id: 0, 716 | text: 'Learn Redux', 717 | completed: false 718 | } 719 | const todoAfter = { 720 | id: 0, 721 | text: 'Learn Redux', 722 | completed: true 723 | } 724 | expect( 725 | toggleTodo(todoBefore) 726 | ).toEqual(todoAfter); 727 | } 728 | 729 | testToggleTodo(); // run the test 730 | 731 | ``` 732 | 733 | Just like in the last lesson, I'm going to start by writing 734 | a *mutating* version that passes the current test. 735 | So a *mutating* version just flips the `completed` field and re-assigns it on the passed `Object` (*the `todo`): 736 | 737 | ```js 738 | const toggleTodo = (todo) => { 739 | todo.completed = !todo.completed; 740 | return todo; 741 | } 742 | ``` 743 | And while it works, 744 | we know that *mutations* are ***not allowed*** in Redux. 745 | So to *enforce* this, I'm calling `deepFreeze` on my `todo` Object 746 | (*in the case of the test `todoBefore`*) 747 | and I'm *not allowed* to change its `completed` field anymore. 748 | 749 | One way out of this would be to create a new object 750 | with every field copied from the original object 751 | *except* the `completed` field which would be flipped: 752 | 753 | ```js 754 | const toggleTodo = (todo) => { 755 | return { 756 | id: todo.id, 757 | text: todo.text, 758 | completed: !todo.completed 759 | } 760 | } 761 | ``` 762 | 763 | However if we later add new properties to the `todo` object, 764 | we might *forget* to update this piece of code to include them. 765 | 766 | This is why I suggest that you use the **ES6** 767 | [`Object.assign`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 768 | method which is *new* to **ES6** 769 | and it lets you `assign` properties of *several* objects onto the target object. Note how the `Object.assign` order corresponds to that of the JavaScript assignment operator. 770 | The left (*first*) argument is the one who's properties 771 | are going to be assigned, so its going to be *mutated*. 772 | This is why we are passing an *empty* `Object` as the *first* argument 773 | so we don't *mutate* any *existing* data. 774 | Every further argument to `Object.assign` will be considered a 775 | "*source*" `Object` who's properties will be copied to the target object. 776 | 777 | It is ***important*** that if several *sources* specify 778 | different values for the *same* property, the ***last*** one "*wins*". 779 | and this is what we use to *overwrite* the `completed` field 780 | despite what the original object says. 781 | 782 | ```js 783 | const toggleTodo = (todo) => { 784 | return Object.assign({}, todo, { 785 | completed: !todo.completed 786 | }); 787 | }; 788 | ``` 789 | 790 | *Finally* you need to *remember* that 791 | [`Object.assign`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 792 | is a *new* method in **ES6** so it is *not natively available* in 793 | all the Browsers (*specifically not supported in Internet Explorer*). 794 | 795 | ![object assign-browser-compatibility](https://cloud.githubusercontent.com/assets/194400/12080810/6fbc745c-b25f-11e5-9e58-ed860ea2872a.png) 796 | 797 | You should use a "*polyfill*" either the one that ships with ***Babel*** 798 | or a *standalone* polyfill to use it (`Object.assign`) without risking 799 | crashing your website. 800 | 801 | Another option that does not require a polyfill is use 802 | the new [`Object` ***spread*** operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator) 803 | which is ***not part of ES6*** however it is *proposed* for **ES7** 804 | it is "*fairly popular*" and it is *enabled* in Babel 805 | if you use the "*stage 2 preset*": 806 | 807 | ```js 808 | const toggleTodo = (todo) => { 809 | return { 810 | ...todo, 811 | completed: !todo.completed 812 | } 813 | }; 814 | ``` 815 | 816 | > Code at the *end* of Video 10: 817 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/63c9e49f7d3ff4611c3c9e91b131102a7729ff30/index.html#L16-L44) 818 | 819 | > **Note**: we have *not* used the `Object` spread operator in 820 | our code because it does not run in *any* browser!! 821 | 822 |
823 | 824 | #### 11. Writing a Todo List Reducer (Adding a Todo) 825 | 826 | > Video: https://egghead.io/lessons/javascript-redux-writing-a-todo-list-reducer-adding-a-todo 827 | 828 | Just like in the previous two lessons, I'm using the 829 | **expect** library to make test assertions and 830 | **deep-freeze** library to *prevent accidental mutations* in my code. 831 | 832 | In this lesson I will create a reducer for a Todo-list application 833 | who's state is described as an array of Todos. 834 | 835 | Just to remind you what a reducer is: its a "*pure function*" 836 | you write to implement the update logic of your application. 837 | That is how the next state is calculated given the current state 838 | and the action being dispatched. 839 | 840 | Before writing a reducer I want a way of knowing whether its code is correct. So I'm starting by writing a test for it: 841 | 842 | 843 | ```js 844 | const todos = (state = [], action) => { 845 | 846 | }; 847 | 848 | const testAddTodo = () => { 849 | const stateBefore = []; 850 | const action = { 851 | type: 'ADD_TODO', 852 | id: 0, 853 | text: 'Learn Redux' 854 | } 855 | const stateAfter = [ 856 | { 857 | id: 0, 858 | text: 'Learn Redux', 859 | completed: false 860 | } 861 | ]; 862 | 863 | deepFreeze(stateBefore); 864 | deepFreeze(stateAfter); 865 | 866 | expect( 867 | todos(stateBefore, action); 868 | ).toEqual(stateAfter); 869 | }; 870 | 871 | testAddTodo(); 872 | 873 | console.log('All tests passed.'); 874 | ``` 875 | 876 | I'm declaring two variables: 877 | + `stateBefore` - the state before, which is an *empty* `Array` 878 | + `action` - the action being dispatched - which is an action describing a user adding a new todo with some `id` and a `text` (*fields*). 879 | 880 | I am also declaring the *state* I `expect` *after* calling the reducer. 881 | and like `stateBefore` it is an `Array`, but this time it has 882 | a *single element* representing the Todo that was just added; 883 | so it has the same `id` and `text` as the action `Object`. 884 | and it *also* has an *additional* field called `completed` 885 | that I want to be *initialized* to be `false` 886 | 887 | We want to make sure that the reducer is a "*pure function*", 888 | so I am calling `deepFreeze` both on the `stateBefore` 889 | *and* the `action`. 890 | 891 | *Finally* I'm ready to use the `expect` library to verify that if I call 892 | the todo reducer with the `stateBefore` and the `action` 893 | I'm going to get the result that is ***deeply*** **equal** 894 | to the `stateAfter` I *just* declared. 895 | 896 | This concludes my *first* test, now I can call it 897 | just like a regular JavaScript function: `testAddTodo();` 898 | And if it doesn't `throw` in the `expect` call I'm going to see a message 899 | that the tests have passed. 900 | 901 | Of *course* it fails because the reducer is not implemented yet; 902 | it's an *empty* function, so it returns `undefined` 903 | instead of the `Array` with a single item that I `expect` in the test. 904 | 905 | To fix this I will need my reducer to take a look at the 906 | `action.type` property which is a `String` 907 | when it matches the `ADD_TODO` string which I specify in my test 908 | to *satisfy* the test I need to return a *new* `Array` 909 | which includes all the items from the *original* `Array` 910 | but *also* a *new* Todo item that has its `id` and `text` 911 | copied from the `action` Object 912 | and a `completed` field set to `false` 913 | 914 | Finally I add a `default` case to my `switch` statement 915 | because *every* reducer has to `return` the *current* state 916 | for any *unknown* `action`: 917 | 918 | ```js 919 | const todos = (state = [], action) => { 920 | switch (action.type) { 921 | case 'ADD_TODO': 922 | return [ 923 | ...state, 924 | { 925 | id: action.id, 926 | text: action.text, 927 | completed: false 928 | } 929 | ]; 930 | default: 931 | return state; 932 | } 933 | }; 934 | ``` 935 | 936 | Now the tests run *successfully*! 937 | 938 | Lets **recap** the *data flow* in this example to see why 939 | first I create the state `Array` (`stateBefore`) 940 | which is an *empty* `Array` 941 | and the `action` Object inside my test function. 942 | I'm passing them as arguments to my *reducer* function called todos 943 | the `todos` reducer accepts the state and action as *arguments* 944 | and takes a look at the `action.type` 945 | in this case the `action.type` is a `String` saying `ADD_TODO` 946 | so it matches the `switch` `case` inside the reducer 947 | the reducer returns a *new* `Array` 948 | which contains the items from the *old* `Array` 949 | (*in the case of our test, the empty `stateBefore` Array*...) 950 | and a *new* item (`Object`) representing the added todo 951 | however the `state` we passed from the test was an *empty* `Array` 952 | so at the end we are going to get an `Array` with a *single* item 953 | which is the *new* todo 954 | *finally* we compare the returned value 955 | to an `Array` with a single todo item 956 | to make sure the reducer works as intended 957 | the equality check passes 958 | so this makes the test successful. 959 | 960 | > Code snapshot for the end of Video 11: 961 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/278a17be1fafe2e0f354aa431e0ad4fc776bbc41/index.html#L15-L56) 962 | 963 |
964 | 965 | #### 12. Writing a Todo List Reducer (Toggling a Todo) 966 | 967 | This lesson picks up where we left off in Video 11, 968 | so make sure you watched that an tried writing/running the code! 969 | 970 | > Video: https://egghead.io/lessons/javascript-redux-writing-a-todo-list-reducer-toggling-a-todo 971 | 972 | In this lesson we will continue creating the reducer for the 973 | todo list application 974 | and the only action that this reducer *currently* handles 975 | is called `ADD_TODO` 976 | we also created a ***test*** that makes sure that when the reducer 977 | is called with an *empty* `Array` and the `ADD_TODO` `action` 978 | it returns an `Array` with a *single* todo element. 979 | 980 | In this lesson we will follow the same approach 981 | to implement another action called `toggleTodo` 982 | 983 | We're going to start with the *test* again 984 | and *this* time we are testing a different action 985 | and we have a different *initial* `state` 986 | the `state` *before* calling the reducer (`stateBefore`) 987 | now includes two different todos with `id` `0` and `1`. 988 | notice how *both* of them have their `completed` field set to `false` 989 | 990 | Next I declare the `action` 991 | and the action is as `Object` with the `type` property 992 | wich is `TOGGLE_TODO` `String` 993 | and the `id` of the todo that I want to be "*toggled*" 994 | I declare the state that I `expect` 995 | to receive *after* calling the reducer (`stateAfter`) 996 | its pretty much the same as *before* calling the reducer 997 | however I `expect` the todo with `id` specified in the `action` 998 | or `1` in this case 999 | to change its `completed` field 1000 | 1001 | ```js 1002 | const testToggleTodo = () => { 1003 | const stateBefore = [ 1004 | { 1005 | id: 0, 1006 | text: 'Learn Redux', 1007 | completed: false 1008 | }, 1009 | { 1010 | id: 1, 1011 | text: 'Go shopping', 1012 | completed: false 1013 | } 1014 | ]; 1015 | const action = { 1016 | type: 'TOGGLE_TODO', 1017 | id: 1 1018 | }; 1019 | const stateAfter = [ 1020 | { 1021 | id: 0, 1022 | text: 'Learn Redux', 1023 | completed: false 1024 | }, 1025 | { 1026 | id: 1, 1027 | text: 'Go shopping', 1028 | completed: true 1029 | } 1030 | ]; 1031 | 1032 | deepFreeze(stateBefore); 1033 | deepFreeze(action); 1034 | 1035 | expect( 1036 | todos(stateBefore, action) 1037 | ).toEqual(stateAfter); 1038 | } 1039 | 1040 | testToggleTodo(); 1041 | console.log('All tests passed.') 1042 | 1043 | ``` 1044 | 1045 | the reducer *must* be a "*pure function*" 1046 | so at a matter of *precaution* I call `deepFreeze` 1047 | on the `state` and the `action` 1048 | 1049 | *Finally*, just like in the previous lesson, I'm asserting 1050 | that the result of calling our reducer with the `stateBefore` and the `action` 1051 | is going to be "*deeply equal*" (`toEqual`) the `stateAfter`. 1052 | 1053 | Now, my test is a function so I need to call it at the end of the file 1054 | And if I run it, it fails because I have not *implemented* 1055 | handling this action yet. 1056 | 1057 | I'm adding a new `switch case` to my reducer 1058 | and I remember that I should not change the original `Array` 1059 | so I'm using the [`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method 1060 | to produce a *new* `Array` 1061 | the function I pass as an argument will be called for *every* todo 1062 | so if its *not* the todo I'm looking for, I don't want to change it, 1063 | so I just `return` it as is. 1064 | *However* if the todo *is* the one we want to toggle 1065 | I'm going to `return` a *new* `Object` 1066 | that *all* the properties of the *original* todo `Object` 1067 | thanks to the `Object` spread operator 1068 | but *also* an *inverted* value of the `completed` field: 1069 | 1070 | ```js 1071 | const todos = (state = [], action) => { 1072 | switch (action.type) { 1073 | case 'ADD_TODO': 1074 | return [ 1075 | ...state, 1076 | { 1077 | id: action.id, 1078 | text: action.text, 1079 | completed: false 1080 | } 1081 | ]; 1082 | case 'TOGGLE_TODO': 1083 | return state.map(todo => { 1084 | if(todo.id !== action.id){ 1085 | return todo; 1086 | } 1087 | return { 1088 | ...todo, 1089 | completed: !todo.completed 1090 | }; 1091 | }); 1092 | default: 1093 | return state; 1094 | } 1095 | }; 1096 | ``` 1097 | 1098 | Now both of our tests run successfully... 1099 | And we have an implementation of the reducer that can add and toggle todos. 1100 | 1101 | > Code at the end of Video 12: 1102 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/92b9a312678ba26ca90050f17d796b26f992de63/index.html#L31-L33) (*using `Object` spread*) 1103 | 1104 | > ***Note***: While the [*spread operator*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Browser_compatibility) 1105 | is an **ES6** *Standard* for `Array`, its only a ***Draft*** for `Object` 1106 | proposed for **ES7** which means it is 1107 | **not** yet **available** in ***any*** **Browser**! 1108 | As such I have modified Dan's 1109 | code to use `Object.assign` (*see Video #10) 1110 | which (*at least*) works in Chrome...: 1111 | 1112 | ```js 1113 | 1114 | case 'TOGGLE_TODO': 1115 | return state.map(todo => { 1116 | if(todo.id !== action.id){ 1117 | return todo; 1118 | } 1119 | return Object.assign({}, todo, { 1120 | completed: !todo.completed 1121 | }); 1122 | }); 1123 | ``` 1124 | 1125 | The *works* in ***ALL Modern Browsers Today (Without Babel)*** way of doing this is: 1126 | ```js 1127 | case 'TOGGLE_TODO': 1128 | return state.map(todo => { 1129 | if(todo.id !== action.id){ 1130 | return todo; 1131 | } 1132 | var keys = Object.keys(todo); // IE9+ 1133 | var toggled = {}; // new object to avoid mutation 1134 | keys.forEach(function(index) { 1135 | toggled.index = todo.index; // copy all properties/values of todo 1136 | }); 1137 | toggled.completed = !todo.completed 1138 | return toggled; 1139 | }); 1140 | 1141 | ``` 1142 | 1143 | We can *probably* ***all*** agree that the code is more *elegant* 1144 | with the ES7 Object spread operator. 1145 | But in the interest of having something that works *now* 1146 | (*without Babel for running this tutorial in a Chrome without a node.js "workflow"...*) 1147 | I've opted to use the `Object.assign` method is an **ES6** *Standard*. 1148 | 1149 | > Code at the end of Video 12: 1150 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/de238f6391dbaf85de9527932c48d7702e4e2336/index.html#L31-L33) (*using `Object.assign`*) 1151 | 1152 |
1153 | 1154 | #### 13. Reducer Composition with Arrays 1155 | 1156 | > Video: https://egghead.io/lessons/javascript-redux-reducer-composition-with-arrays 1157 | 1158 | In the *previous* lesson we created a *reducer* 1159 | that can handle two actions: adding a *new* todo 1160 | and toggling an *existing* todo. 1161 | Right now the code to *update* the todo item 1162 | or to *create* a new one is placed right inside the todos reducer 1163 | this function is hard [*difficult*] to understand 1164 | because it mixes two different concerns: 1165 | how the todos `Array` is updated *and* 1166 | how individual todos (`Objects`) are updated. 1167 | This is not a problem *unique* to Redux 1168 | any time a function does *too many* things 1169 | you want to *extract* other functions from it and call them 1170 | so that every function only addresses a single concern 1171 | 1172 | In this case I decided that creating and updating a todo 1173 | in response to an `action` is a *separate* operation 1174 | and needs to be handled by a *separate* function called `todo` 1175 | 1176 | ```js 1177 | const todo = (state, action) => { 1178 | switch (action.type) { 1179 | case 'ADD_TODO': 1180 | return { 1181 | id: action.id, 1182 | text: action.text, 1183 | completed: false 1184 | } 1185 | case 'TOGGLE_TODO': 1186 | if (state.id !== action.id) { 1187 | return state; 1188 | } 1189 | return Object.assign({}, state, { // see: https://git.io/vuBzV 1190 | completed: !state.completed // here state is the individual todo 1191 | }); 1192 | default: 1193 | return state; 1194 | } 1195 | } 1196 | 1197 | const todos = (state = [], action) => { 1198 | switch (action.type) { 1199 | case 'ADD_TODO': 1200 | return [ 1201 | ...state, 1202 | todo(undefined, action) 1203 | ]; 1204 | case 'TOGGLE_TODO': 1205 | return state.map(t => todo(t, action)); 1206 | default: 1207 | return state; 1208 | } 1209 | }; 1210 | ``` 1211 | 1212 | As a matter of convention I decided that it should also accept 1213 | two arguments: the *current* `state` and the `action` being dispatched 1214 | and it should `return` the *next* `state`. 1215 | But in this case the `state` refers to the *individual* todo 1216 | and *not* to the *list* (`Array`) of todos. 1217 | 1218 | *Finally* there is no "*magic*" in Redux to make it work. 1219 | We extracted the todo reducer from the todos reducer. 1220 | So now we need to call it for every todo 1221 | and assemble the results into an `Array`. 1222 | While this is not required in this particular example 1223 | I suggest that you *always* have the `default` case 1224 | where you `return` the *current* `state` 1225 | to avoid "*odd bugs*" in the future. 1226 | 1227 | The ***Pattern*** described in this lesson 1228 | is *pervasive* in Redux development 1229 | and is called "[***Reducer Composition***](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers)". 1230 | 1231 | Different reducers specify how different parts of the `state` tree 1232 | are updated in response to different actions. 1233 | Reducers are also "*normal*" JavaScript functions 1234 | so they can call *other* reducers 1235 | to delegate and abstract away handling of updates 1236 | of some parts of the `state` they manage 1237 | this pattern can be applied *many* times 1238 | and while there is still a *single* top-level reducer 1239 | managing the `state` of your app 1240 | you will find it convenient 1241 | to express it as *many* reducers calling each other 1242 | each contributing to a *part* of the application `state` tree. 1243 | 1244 | 1245 | > Recap: this video/lesson was simply to show how 1246 | to extract a method from inside the *main* ("*top-level*") 1247 | reducer and have a *separate* reducer 1248 | which handles updates on the *individual* todo items. 1249 | > While there are ***more lines of code***, 1250 | > the *separate* reducers are ***more maintainable*** 1251 | 1252 | > Note that the tests from the *previous* video still pass 1253 | after we have created the `todo` reducer, 1254 | because the *functionality* has not changed, 1255 | its merely been separated to simplify the *main* reducer. 1256 | 1257 | > Code at the end of Video 13: 1258 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/89572d3b8e751c2fe11d8a1750ea4f82d64d5e3c/index.html#L15-L47) 1259 | 1260 |
1261 | 1262 | #### 14. Reducer Composition with Objects 1263 | 1264 | Tip: This tutorial builds upon the code written in Video/Lesson 13. 1265 | If you skipped it, or left a break between watching the videos, 1266 | go back and re-acquaint yourself before proceeding. 1267 | 1268 | > Video: https://egghead.io/lessons/javascript-redux-reducer-composition-with-objects 1269 | 1270 | In the *previous* lesson we established 1271 | the *pattern* of "*Reducer Composition" 1272 | where one reducer can be called by another reducer 1273 | to update items inside an array. 1274 | If we create a `store` with this reducer and log its `state` 1275 | we will find that the *initial* `state` of it 1276 | is an *empty* `Array` of todos 1277 | and if we *dispatch* an `ADD_TODO` `action` 1278 | we will find that the *corresponding* todo has been added 1279 | to the `state` `Array` 1280 | if we *dispatch* *another* `ADD_TODO` `action` 1281 | the *corresponding* todo will *also* be added at the end of the `Array`. 1282 | And dispatching a `TOGGLE_TODO` `action` with `id` (*set to*) `0` 1283 | will flip the `completed` field of the todo with `id` *zero* (`0`). 1284 | 1285 | > The new code not in the previous tutorial is: 1286 | 1287 | ```js 1288 | 1289 | const { createStore } = Redux; 1290 | const store = createStore(todos); 1291 | 1292 | console.log('Initial state:'); 1293 | console.log(store.getState()); 1294 | console.log('--------------'); 1295 | 1296 | console.log('Dispatching ADD_TODO.'); // first todo 1297 | store.dispatch({ 1298 | type: 'ADD_TODO', 1299 | id: 0, 1300 | text: 'Learn Redux' 1301 | }); 1302 | 1303 | console.log('Current state:'); 1304 | console.log(store.getState()); 1305 | console.log('--------------'); 1306 | 1307 | console.log('Dispatching ADD_TODO.'); // second todo 1308 | store.dispatch({ 1309 | type: 'ADD_TODO', 1310 | id: 1, 1311 | text: 'Go shopping' 1312 | }); 1313 | 1314 | console.log('Current state:'); 1315 | console.log(store.getState()); 1316 | console.log('--------------'); 1317 | 1318 | console.log('Dispatching TOGGLE_TODO.'); // toggle first todo 1319 | store.dispatch({ 1320 | type: 'TOGGLE_TODO', 1321 | id: 0 1322 | }); 1323 | 1324 | console.log('Current state:'); 1325 | console.log(store.getState()); 1326 | console.log('--------------'); 1327 | ``` 1328 | 1329 | > or you can run: [`index.html`](https://github.com/nelsonic/learn-redux/blob/33a46dbc0eb733f6494fdb8de89d91dde58c1731/index.html#L50-L87) 1330 | (Code Snapshot for Video 14 @ 0:40) 1331 | > which has the following developer console *output*: 1332 | 1333 | ![learn-redux-output-of-video-14-console logs](https://cloud.githubusercontent.com/assets/194400/12122835/6dc1bc44-b3d5-11e5-8e4d-691bdd86f910.png) 1334 | 1335 | 1336 | Representing the *whole* `state` of the application 1337 | as an `Array` of todos works for a *simple* example 1338 | but what if we want to store *more* information? 1339 | For *example* we may want to let the user choose which todos 1340 | are *currently* *visible* with a `visibilityFilter` 1341 | such as `SHOW_COMPLETED`, `SHOW_ALL` or `SHOW_ACTIVE`. 1342 | 1343 | ```js 1344 | const visibilityFilter = ( 1345 | state = 'SHOW_ALL', // default state 1346 | action 1347 | ) => { 1348 | switch (action.type) { 1349 | case 'SET_VISIBILITY_FILTER': 1350 | return action.filter; 1351 | default: 1352 | return state; 1353 | } 1354 | }; 1355 | ``` 1356 | 1357 | The `state` of the `visibilityFilter` is a *simple* `String` 1358 | representing the *current* filter 1359 | and it is *changed* by the `SET_VISIBILITY_FILTER` `action`. 1360 | 1361 | ```js 1362 | const todoApp = (state = {}, action) => { 1363 | return { 1364 | todos: todos( 1365 | state.todos, 1366 | action 1367 | ), 1368 | visibilityFilter: visibilityFilter( 1369 | state.visibilityFilter, 1370 | action 1371 | ) 1372 | }; 1373 | }; 1374 | ``` 1375 | 1376 | To *store* this *new* information, I don't need to *change* 1377 | the *existing* reducers, I will use the Reducer Composition Pattern 1378 | and create a *new* reducer that *calls* the existing reducers 1379 | to manage parts of its state 1380 | and combines the results in a *single* `state` `Object` 1381 | ***note*** that the first time it runs, it will pass `undefined` as the `state` 1382 | to the "*child*" reducers because the *initial* state 1383 | of the *combined* reducer is an *empty* `Object` 1384 | so all its fields are `undefined` 1385 | this gets the "*child*" reducers to return their *initial* `state` 1386 | and populates the `state` `Object` for the first time. 1387 | 1388 | > Code Snapshot for Video 14 @ 1:45 1389 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/7fef9d1ede97b48a03a4e55c6b8f10bdcc0b5a89/index.html#L62-L73) 1390 | 1391 | When an `action` comes in, it calls the reducers 1392 | with the parts of the `state` that they manage and the `action` 1393 | and combines the result into the *new* `State` object. 1394 | This is *another* example of the Reducer Composition Pattern 1395 | but this time we use it to *combine* *several* reducers 1396 | into a single reducer that we will now use to create our `store`. 1397 | 1398 | The *initial* `state` of the *combined* reducer now contains 1399 | the *initial* `state` of the *independent* reducers 1400 | and any time an `action` comes in those reducers handle the action 1401 | *independently* this pattern helps *scale* Redux development 1402 | because different people on the team 1403 | can work on different reducers handling the *same* actions 1404 | without running into each other and causing merge conflicts. 1405 | 1406 | ```js 1407 | console.log('Dispatching SET_VISIBILITY_FILTER'); 1408 | store.dispatch({ 1409 | type: 'SET_VISIBILITY_FILTER', 1410 | filter: 'SHOW_COMPLETED' 1411 | }); 1412 | 1413 | console.log('Current state:'); 1414 | console.log(store.getState()); 1415 | console.log('--------------'); 1416 | ``` 1417 | 1418 | > Note: This `action` is merely setting the `visibilityFilter` *property* 1419 | of the `store` `Object` 1420 | 1421 | ![console-log-output-for-set_visibility_filter](https://cloud.githubusercontent.com/assets/194400/12123718/753f2a92-b3da-11e5-982a-17be0a075f1b.png) 1422 | 1423 | 1424 | > ***NOTE***: this code does not actually do anything 1425 | to the ***UI** *yet*. (*be patient that's next...*) 1426 | 1427 | *Finally* I'm dispatching the `SET_VISIBILITY_FILTER` `action` 1428 | and you can see that it does not affect the todos 1429 | but the `visibilityFilter` field has been updated. 1430 | 1431 | > Code Snapshot for *End* of Video 14: 1432 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/9702c1c858b4a22fff85339c55cf914ae3969666/index.html#L115-L123) 1433 | 1434 |
1435 | 1436 | #### 15. Reducer Composition with `combineReducers`() 1437 | 1438 | > Video: https://egghead.io/lessons/javascript-redux-reducer-composition-with-combinereducers 1439 | 1440 | In the previous lesson we learned how to use 1441 | the "*Reducer Composition*" Pattern 1442 | to let different reducers handle different parts of the `state` tree 1443 | and then *combine* their results. 1444 | In fact this pattern is *so* common 1445 | that it's present in *most* Redux applications. 1446 | And this is why Redux provides a function called `combineReducers` 1447 | that lets you avoid writing this code by *hand* 1448 | and instead it *generates* the top-level reducer *for* you. 1449 | 1450 | The *only* argument to `combineReducers` is an `Object` 1451 | and this `Object` lets me specify the *mapping* between 1452 | the `state` field names and the reducers managing them: 1453 | 1454 | ```js 1455 | const { combineReducers } = Redux; 1456 | const todoApp = combineReducers({ 1457 | todos: todos, 1458 | visibilityFilter: visibilityFilter 1459 | }); 1460 | ``` 1461 | 1462 | The returned value of the `combineReducers` call is a reducer function 1463 | which is pretty much equivalent to 1464 | the reducer function I wrote by hand previously [*See: Video 14 above*] 1465 | 1466 | The keys of the `Object` that I configure `combineReducers` with 1467 | correspond to the fields of the `state` `Object` its going to manage. 1468 | 1469 | The *values* of the `Object` I pass to combine reducers are the reducers 1470 | it should call to update the corresponding `state` fields 1471 | this *combined* reducer call says that 1472 | the todos field inside the state object manages 1473 | will be updated by the `todos` reducer 1474 | and the `visibilityFilter` field inside the the `state` `Object` 1475 | will be will be updated by calling the `visibilityFilter` reducer 1476 | and the results will be assembled into a *single* `Object` 1477 | in other words it behaves pretty much exactly as the function 1478 | commented out below: 1479 | 1480 | ```js 1481 | // const todoApp = (state = {}, action) => { 1482 | // return { 1483 | // todos: todos( 1484 | // state.todos, 1485 | // action 1486 | // ), 1487 | // visibilityFilter: visibilityFilter( 1488 | // state.visibilityFilter, 1489 | // action 1490 | // ) 1491 | // }; 1492 | // }; 1493 | ``` 1494 | 1495 | *Finally* I will establish a *useful* convention: 1496 | I will *always* name my reducers after the `state` keys they manage 1497 | since the *key* names and the *value* names are now the *same* 1498 | I can *omit* the *values* thanks to the **ES6** Object Literal 1499 | ***Shorthand Notation***: 1500 | 1501 | ```js 1502 | const { combineReducers } = Redux; 1503 | const todoApp = combineReducers({ 1504 | todos, 1505 | visibilityFilter 1506 | }); 1507 | ``` 1508 | 1509 | In this lesson you learned how to *generate* a single reducer 1510 | that calls many reducers to manage parts of its state 1511 | by using the `combineReducers` utility function. 1512 | 1513 | > Code Snapshot for *End* of Video 15 1514 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/128196f61fe5d0e752b16625e8676961f74858ff/index.html#L62-L66) 1515 | 1516 | 1517 | ##### Read more about ES6 Object Literal *Shorthand* Notation 1518 | 1519 | + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015 1520 | + Good examples: https://eslint.org/docs/rules/object-shorthand.html (*by Nicholas C. Zakas*) 1521 | 1522 | > ***NOTE***: as *usual*, while **ES6** Object Literal *Shorthand* Notation is a *Standard* it is *still* not implemented in the *majority* of web browsers: 1523 | 1524 | ![es6-object-literal-shorthand-notation](https://cloud.githubusercontent.com/assets/194400/12127256/0d4190d8-b3ee-11e5-9191-8c4a532ad59f.png) 1525 | 1526 | > *I have a* ***strong bias*** *towards* ***explicitly*** 1527 | *typing the* ***Values*** *in an `Object` literal for clarity*. 1528 | *But given the* ***naming convention*** *in Redux*, 1529 | *its pretty safe to omit them in this case*. 1530 | 1531 |
1532 | 1533 | #### 16. Implementing combineReducers() from Scratch 1534 | 1535 | > Video: https://egghead.io/lessons/javascript-redux-implementing-combinereducers-from-scratch 1536 | 1537 | In the previous lesson we learned to use the `combineReducers` function 1538 | which comes with Redux and generates one reducer 1539 | from several other reducers 1540 | delegating to them parts of the `state` tree. 1541 | 1542 | To gain a *deeper* understanding of how *exactly* `combineReducers` works 1543 | we will *implement* it ***from scratch*** in this lesson. 1544 | 1545 | `combineReducers` is a function so I'm writing a function declaration 1546 | and its only argument is the *mapping* between `state` keys 1547 | and the reducers, so I'm just going to call it `reducers`. 1548 | 1549 | ```js 1550 | // const { combineReducers } = Redux; 1551 | const combineReducers = (reducers) => { 1552 | return (state = {}, action) => { 1553 | return Object.keys(reducers).reduce( 1554 | (nextState, key) => { 1555 | nextState[key] = reducers[key]( 1556 | state[key], 1557 | action 1558 | ); 1559 | return nextState; 1560 | }, 1561 | {} // empty initial nextState 1562 | ); 1563 | }; 1564 | }; 1565 | ``` 1566 | 1567 | The returned value is supposed to be a reducer itself 1568 | so this is a function that returns another function 1569 | and the signature of the returned function is a reducer signature 1570 | it has the `state` and the `action` 1571 | now I'm calling the [`Object.keys` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) (*IE9+*) 1572 | which gives me all the `keys` of the `reducers` `Object` 1573 | in our example this is `todos` and `visibilityFilter` 1574 | next I am calling the (Array) `reduce` method on the `keys` 1575 | because I want to produce a *single* value such as the `nextState` 1576 | by *accumulating* over every reducer `key` 1577 | and calling the *corresponding* reducer 1578 | each reducer passed to the combined reducers function is only responsible 1579 | for updating a *part* of the `state` 1580 | this is why I'm saying that the `nextState` by the given `key` 1581 | can be calculated by calling 1582 | the *corresponding* reducer by the *given* `key` with 1583 | the *current* `state` by the *given* `key` 1584 | and the `action` 1585 | the `Array.reduce` wants me to `return` the *next* accumulated value 1586 | from the callback so I am returning the `nextState` 1587 | and I'm also specifying an *empty* `Object` as the *initial* `nextState` 1588 | *before* all the `keys` are processed. 1589 | And there we have it this is a *working* re-implementation of 1590 | `combineReducers` utility from Redux 1591 | 1592 | Lets briefly *recap* how it works. 1593 | I'm calling `combineReducers` with an `Object` 1594 | who's values are the reducer functions 1595 | and keys are the `state` fields they manage 1596 | inside the *generated* reducer I'm retrieving all the keys 1597 | of the reducers I passed to `combineReducers` which is 1598 | an `Array` of `Strings` (*specifically*) 1599 | `todos` and `visibilityFilter` (*in our example*). 1600 | I'm starting with an *empty* `Object` for my `nextState` 1601 | and I'm using the `Reduce` operation over these `keys` 1602 | to fill it *gradually*. 1603 | Notice that I am *mutating* the `nextState` `Object` on every iteration 1604 | this is not a problem because it is an `Object` I created 1605 | *inside* the reducer it is not something passed from *outside* 1606 | so reducer stays a "*pure*" function. 1607 | To calculate the `nextState` for a given `key` 1608 | it calls the corresponding reducer function 1609 | such as `todos` or `visibilityFilter` 1610 | the *generated* reducer will pass to the *child* reducer 1611 | only a *part* of its `state` by the `key` 1612 | so if its `state` is a *single* `Object` 1613 | its only going to pass the *relevant* part 1614 | such as `todos` or `visibilityFilter` 1615 | depending on the *current* `key` 1616 | and save the result in the `nextState` by the same `key` 1617 | 1618 | *Finally* we use the `Array.reduce` operation (*method*) 1619 | with the *empty* `Object` as the *initial* `nextState` 1620 | that is being filled on every iteration 1621 | until it is the return value of the whole `.reduce` operation 1622 | 1623 | In this lesson you learned how to implement the `combineReducers` 1624 | utility that comes with Redux from *scratch* 1625 | it is not *essential* to use in Redux 1626 | so it is *fine* if you don't fully understand how it works *yet* 1627 | however it is a *good* [*great*!] idea to 1628 | *practice* functional programming 1629 | and *understand* that functions can take other functions 1630 | and `return` other functions 1631 | because knowing this will help you get more productive in Redux 1632 | in the long term. 1633 | 1634 |
1635 | 1636 | #### 17. React Todo List Example (Adding a Todo) 1637 | 1638 | > Video: https://egghead.io/lessons/javascript-redux-react-todo-list-example-adding-a-todo 1639 | 1640 | In the *previous* lesson we learned how to split the "*root*" reducer 1641 | into *many* smaller reducers that manage *parts* of the `state` tree 1642 | and now we have a ready `todoApp` reducer 1643 | that handles all the `actions` of our simple todo application. 1644 | 1645 | So now it's time to *implement* the ***view layer*** 1646 | and I'm going to use React in this example 1647 | 1648 | I'm adding **React** and [React-DOM](https://facebook.github.io/react/docs/glossary.html) packages from the Facebook CDN 1649 | and I'm also adding a div with `id='root'` 1650 | which is where I'm going to `render` my React application 1651 | 1652 | ```js 1653 | 1654 | 1655 | ``` 1656 | 1657 | Similar to the React *Counter* example from the 8th Lesson 1658 | I declare a `render` function that is going to update the DOM 1659 | in response to the *current* application `state` 1660 | and I'm going to `subscribe` to the `store` changes 1661 | and call `render` when ever the `store` changes 1662 | and *once* to `render` the *initial* state: 1663 | 1664 | ```js 1665 | const { createStore } = Redux; 1666 | const store = createStore(todoApp); 1667 | 1668 | const render = () => { 1669 | // gets expanded below ... 1670 | }; 1671 | 1672 | store.subscribe(render); 1673 | render(); 1674 | ``` 1675 | 1676 | And the *implementation* of the `render` method 1677 | is going to use React so its calling `ReactDOM.render` 1678 | for some `TodoApp` *component* I haven't written *yet* 1679 | and it renders it into the `div` I created inside the `html` 1680 | called `root`. 1681 | 1682 | React provides a "*base*" `Class` for all components 1683 | so I'm grabbing it from the React `Object` 1684 | its called `React.Component` 1685 | and I'm declaring my own `TodoApp` component 1686 | that `extends` the React "*base*" `Component` 1687 | 1688 | ```js 1689 | const { Component } = React; 1690 | 1691 | class TodoAPP extends Component { 1692 | // filled out below ... 1693 | } 1694 | 1695 | const render = () => { 1696 | ReactDOM.render( 1697 | , 1698 | document.getElementById('root') 1699 | ); 1700 | }; 1701 | ``` 1702 | 1703 | This `Component` is only going to have a `render` function 1704 | and is going to return a `
` 1705 | and *inside* the `
` I'm going to place a ` 1742 |
    1743 | {this.props.todos.map(todo => 1744 |
  • 1745 | {todo.text} 1746 |
  • 1747 | )} 1748 |
1749 |
1750 | ); 1751 | } 1752 | } 1753 | 1754 | const render = () => { 1755 | ReactDOM.render( 1756 | , 1757 | document.getElementById('root') 1758 | ); 1759 | }; 1760 | ``` 1761 | 1762 | > Code snapshot for Video 17 @ 02:56: 1763 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/c44025b2ea3ed7f8cd4b1120c4aad7f98cce2c97/index.html#L81-L104) 1764 | 1765 | If you open the file in your browser, you should expect to see: 1766 | 1767 | ![learn-redux-video-17-example](https://cloud.githubusercontent.com/assets/194400/12147411/a1bb3974-b490-11e5-8ab0-b011dfecc959.png) 1768 | 1769 | 1770 | You can see that there is a ` 2373 |
2374 | ); 2375 | } 2376 | ``` 2377 | 2378 | The *Functional* Components don't have instances 2379 | however instead of using `this`, 2380 | I can use a *local* variable called `input` 2381 | that I'm going to "*close over*" so that I can write to it 2382 | in the callback ref and I can read from it in the event handler. 2383 | Like previously I want it to be a "*Presentational*" Component 2384 | and not specify behavior 2385 | so I'm just calling a function called `onAddClick` 2386 | passing the *current* `input` value and I make `onAddClick` a prop 2387 | so that the Component that *uses* `AddTodo` 2388 | can specify what happens when `Add` button is clicked. 2389 | 2390 | Again, the `TodoApp` Component acts as a "*Container*" Component 2391 | for the `AddTodo` and it specifies that when the *add* ` 3723 | 3724 | ); 3725 | } 3726 | AddTodo = connect( 3727 | state => { 3728 | return {}; 3729 | }, 3730 | dispatch => { 3731 | return { dispatch }; 3732 | } 3733 | )(AddTodo); 3734 | ``` 3735 | 3736 | The *generated* *Container* Component 3737 | will not pass any `props` dependent on the `state`, 3738 | but it will pass `dispatch` itself 3739 | as a `function` so that the Component can read it from the `props` 3740 | and use it without worrying about *context* 3741 | or specifying `contextTypes`. 3742 | However it is *wasteful* to even `subscribe` to the `store` 3743 | if we don't calculate any `props` from its `state`, 3744 | so I'm *replacing* the `mapStateToProps` function with a `null`, 3745 | which tells `connect` that there is *no need* 3746 | to `subscribe` to the `store`. 3747 | Additionally its a pretty *common pattern* 3748 | to *inject just the `dispatch` function*, 3749 | so this is why if you specify `null` or any "*falsy*" value 3750 | in `connect` as the *second* argument 3751 | you're going to get `dispatch` injected as a `prop`. 3752 | 3753 | So in fact I can just ***remove all arguments*** here 3754 | and the *default* behavior will be to *not* `subscribe` 3755 | to the `store` and to *inject* just the `dispatch` function 3756 | as a `prop`. 3757 | 3758 | ```js 3759 | AddTodo = connect()(AddTodo); 3760 | ``` 3761 | 3762 | 03:44 - Let's re-cap what happens to the Components here: 3763 | The `AddTodo` Component that I declare 3764 | accepts `dispatch` as a `prop`, 3765 | but it doesn't know how to *get* the `store`. 3766 | It just "*hopes*" that "*someone*" is going 3767 | to *pass* `dispatch` to it. 3768 | The `connect` call without any arguments is going 3769 | to *generate* a *Container* Component 3770 | that does *not* `subscribe` to the `store`. 3771 | However that will pass `dispatch` to the Component that it *wraps* 3772 | and in this case it *wraps* my `AddTodo` Component 3773 | 3774 | The *second* `connect` call returns the *generated* 3775 | *Container* Component and I'm assigning it to `AddTodo`, 3776 | so I'm re-assigning the `let` binding the second time, 3777 | and when the further code references `AddTodo` 3778 | it's going to reference the *Container* Component 3779 | that does not need the `dispatch` prop 3780 | and that will pass the `dispatch` prop 3781 | to my *inner* `AddTodo` Component 3782 | that I don't have a reference to anymore. 3783 | 3784 | > If you did not fully understand *why* Dan did this re-factoring, 3785 | read the docs: https://redux.js.org/tutorials/fundamentals/part-5-ui-react 3786 | 3787 | > Complete code for the *end* of **Video 28**: 3788 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/c76e0f0efb7ac1f8883d0727bd244224ee9c6741/index.html) 3789 | 3790 |
3791 | 3792 | #### 29. Generating Containers with connect() from React Redux (FooterLink) 3793 | 3794 | > Video: https://egghead.io/lessons/javascript-redux-generating-containers-with-connect-from-react-redux-footerlink 3795 | 3796 | *Finally* let's take a look at the `FilterLink` 3797 | *Container* Component that renders a `Link` with an `active` property 3798 | and a *click* handler. 3799 | *First* I will write the `mapStateToProps` function 3800 | which I will call `mapStateToLinkProp` 3801 | because I have everything in a *single* file. 3802 | And it's going to accept the `state` of the Redux `store` 3803 | and `return` the `props` that should be passed to the `Link` 3804 | and we only have a *single* such `prop` called `active` 3805 | that determines whether the link displays 3806 | the *current* `visiblityFilter`. 3807 | When I *paste* this *expression* from the `render` method 3808 | I see that it references the `filter` prop 3809 | of the `FilterLink` Component. 3810 | To tell whether a link is *active* we need to 3811 | compare this `prop` with with the `visibilityFilter` 3812 | from the Redux `store` `state` 3813 | it is fairly common to use the *Container* `props` 3814 | when calculating the *Child* `props` 3815 | so this is why `props` are passed as a *second* argument 3816 | to `mapStateToProps` [*or in this example `mapStateToLinkProps`*] 3817 | I will re-name it [*the `mapStateToLinkProps` second argument*] 3818 | to `ownProps` to make it clear that 3819 | we are talking about the *Container* Component's *own* `props` 3820 | and not the `props` that are *passed* to the *Child* 3821 | which is the `return` value of `mapStateToLinkProps` 3822 | 3823 | ```js 3824 | const mapStateToLinkProps = ( 3825 | state, 3826 | ownProps 3827 | ) => { 3828 | return { 3829 | active: 3830 | ownProps.filter === 3831 | state.visibilityFilter 3832 | } 3833 | } 3834 | ``` 3835 | 3836 | The second function I'm writing here is `mapDispatchToProps` 3837 | or to avoid name clashes in the JSBin [*single file app*] 3838 | `mapDispatchToLinkProps`. 3839 | The only argument so far is the `dispatch` function 3840 | and I'm going to need to look at the *Container* Component 3841 | I wrote by hand to see what `props` depend on the `dispatch` function. 3842 | In this case this is just the `onClick` handler 3843 | where I `dispatch` the `SET_VISIBILITY_FILTER` `action`. 3844 | the only `prop` I'm passing down is called `onClick` 3845 | and I declare it as an **ES6** [***Arrow Function***]() 3846 | with no arguments and I *paste* the `dispatch` call. 3847 | 3848 | ```js 3849 | const mapDispatchToLinkProps = ( 3850 | dispatch 3851 | ) => { 3852 | return { 3853 | onClick: () => { 3854 | dispatch({ 3855 | type: 'SET_VISIBILITY_FILTER', 3856 | filter: props.filter 3857 | }); 3858 | } 3859 | }; 3860 | } 3861 | ``` 3862 | 3863 | But it references the `props` *again*, 3864 | so I need to add `ownProps` as an *argument* 3865 | the *second* argument to `mapDispatchToLinkProps` function 3866 | thus: 3867 | 3868 | ```js 3869 | const mapDispatchToLinkProps = ( 3870 | dispatch, 3871 | ownProps 3872 | ) => { 3873 | return { 3874 | onClick: () => { 3875 | dispatch({ 3876 | type: 'SET_VISIBILITY_FILTER', 3877 | filter: ownProps.filter 3878 | }); 3879 | } 3880 | }; 3881 | } 3882 | ``` 3883 | 3884 | *Finally* I will call the `connect` function from `ReactRedux` Library 3885 | to generate the `FilterLink` *Container* Component 3886 | I pass the relevant `mapStateToProps` function 3887 | as the *first* argument [*in our case `mapStateToLinkProps`*] 3888 | the `mapDispatchToProps` as the *second* argument 3889 | [*or `mapDispatchToLinkProps` in our case*] 3890 | and I call the function *again* with the `Link` Component 3891 | which should be *rendered*: 3892 | 3893 | ```js 3894 | const FilterLink = connect( 3895 | mapStateToLinkProps, 3896 | mapDispatchToLinkProps 3897 | )(Link); 3898 | ``` 3899 | 3900 | Now I can *remove* the "*old*" `FilterLink` implementation. 3901 | 3902 | Let's re-cap the *data flow* in this example: 3903 | the `Footer` Component renders **3** `FilterLink` 3904 | and each of them has a different `filter` prop 3905 | that specifies which `filter` it corresponds to 3906 | this `prop` will be available on the `ownProps` `Object` 3907 | that *both* `mapDispatchToProps` and `mapStateToProps` 3908 | will receive as the *second* argument. 3909 | we pass these two functions to the `connect` call 3910 | which will return a *Container* Component called `FilterLink`. 3911 | The `FilterLink` will take the `props` 3912 | that we `return` from the `mapDispatchToProps` and `mapStateToProps` 3913 | [*or the "Link" versions of these in our case...*] 3914 | and pass them as `props` the `Link` Component that it *wraps* 3915 | we can now use the `FilterLink` *Container* Component 3916 | and specify *just* the `filter` 3917 | but the *underlying* `Link` Component 3918 | will receive the *calculated* `active` and `onClick` values. 3919 | 3920 | > Complete Code at the *end* of **Video 29**: 3921 | [`index.html`](https://github.com/nelsonic/learn-redux/blob/f3c0a045b0694714e0ac7060cb1c77efae7d7baa/index.html) 3922 | 3923 |
3924 | 3925 | #### 30. Extracting Action Creators 3926 | 3927 | > Video: https://egghead.io/lessons/javascript-redux-extracting-action-creators 3928 | 3929 | So far we have covered the *Container* Components 3930 | the *Presentational* Components, 3931 | the Reducers and the `store`, 3932 | but we have not covered the concept of `action` *Creators* 3933 | which you might see in the Redux *talks* and *examples*. 3934 | 3935 | Let's consider the following example: 3936 | I `dispatch` the `ADD_TODO` `action` 3937 | from inside the `