{"id":20409126,"url":"https://github.com/tcorral/redux-course","last_synced_at":"2026-05-29T06:31:32.688Z","repository":{"id":141832448,"uuid":"120772365","full_name":"tcorral/redux-course","owner":"tcorral","description":"notes and code from finished 'learn redux' course 📚","archived":false,"fork":false,"pushed_at":"2017-07-03T20:40:47.000Z","size":124,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-22T18:02:53.981Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tcorral.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-08T14:32:58.000Z","updated_at":"2020-02-12T22:15:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"bbf3582e-b15a-4638-8ab1-e86682c6c268","html_url":"https://github.com/tcorral/redux-course","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tcorral/redux-course","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcorral%2Fredux-course","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcorral%2Fredux-course/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcorral%2Fredux-course/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcorral%2Fredux-course/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tcorral","download_url":"https://codeload.github.com/tcorral/redux-course/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcorral%2Fredux-course/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33640627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T05:39:44.291Z","updated_at":"2026-05-29T06:31:32.655Z","avatar_url":"https://github.com/tcorral.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redux Course Notes (abridged)\n\u003cdetails\u003e\n\u003csummary\u003emain ideas of redux\u003c/summary\u003e\n\n1. all state lives in a giant object, a store.\n2. we update the store with 'actions'\n3. we have 'action creators' that create actions that include what happened and a payload of info thats needed (ex: where did this happen)\n4. when the actions get dispatched, they get handled by a reducer\n5. the reducer is responsible for updating your state.\n\n* we used `matchStateToProps()` and `matchDispatchToProps()` in order to expose our state and our action functions to our components (using `connect()`).\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eStore\u003c/summary\u003e\nIn redux, we keep all our data in a 'store'...rather than holding our component state in the component, we just contain it in one giant object.\n\nin store.js:\n```\nimport { createStore, compose } from 'redux';\nimport { syncHistoryWithStore } from 'react-router-redux';\nimport { browserHistory } from 'react-router';\n\n//import the root reducer\nimport rootReducer from './reducers/index';\n\n//we need some default data to work with\nimport comments from './data/comments';\nimport posts from './data/posts';\n\n// create an object for the default data\nconst defaultState = {\n   posts,\n   comments\n};\n\n//create our store (IDK what this stuff is)\nconst store = createStore(rootReducer, defaultState);\n\n//create our history with the store to be exported (IDK what this method does)\nexport const history = syncHistoryWithStore(browserHistory, store);\n\n//export our store\nexport default store;\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eActions\u003c/summary\u003e\n* actions are something that happens in your app (someone clicks a photo, someone likes a photo, someone deletes a comment, etc). whenever this happens, someone dispatches an action.\n\nThe dispatch action has 2 things:\n1. type of action that happened (EX: 'incremementLikes')\n2. a payload of info that is needed (EX: which comment got deleted, what comment was added)..just info about what specifically happened\n\n* the dispatch action contains 'what happened' and 'where'.\n\nan excerpt from 'actions/actionCreators.js':\n```\nexport function increment(index) {\n   return {\n      type: 'INCREMENT_LIKES',\n      index    //this is the es6 shortened version of 'index:index'\n   }\n}\n```\n\n^ this is called an 'action creator' because the object returned is considered the 'action' but the function itself is the 'creator' that will dispatch it out when called.\n\nweird? the function is the creator, the object returned is the action.\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eReducers\u003c/summary\u003e\nnext we'll need to create the second part of these action creators, so when these actions get fired or dispatched, we actually handle the data (we update those likes, we add that comment, etc)...we do this with REDUCERS.\n\n* actions/action creators get fired off or 'dispatched' with info about what just happened. what it DOES NOT do is update our state (in redux, our \"store\"). we need to create a 'reducer' to do this.\n\n* think about event handlers: there's events (click, onChange, keyDown, etc) and those events will happen, but if theres no listener listening to that click, nothing will happen. 'Reducers' listen to those events.\n\n* reducers are created for every piece of state (to update it duh!!).\n\nLets create a reducer (in reducers/posts.js):\n```\n// a reducer takes in 2 things:\n\n//1. the action (what happened)\n//2. a copy of current state\n\nfunction posts(state =[], action) {\n   console.log(state, action);\n   return state;\n}\n\nexport default posts;\n```\n\n* Evidently we can only have ONE MAIN REDUCER, which we put all of our other reducers into. so in our app (at this point) we've created `reducers/posts.js` and `reducers/comments.js`. we need to combine the 2 reducers in one file, which will be `reducers/index.js`:\n\n```\nimport { combineReducers } from 'redux';\nimport { routerReducer } from 'react-router-redux';\n\nimport posts from './posts';\nimport comments from './comments';\n\n\nconst rootReducer = combineReducers({\n   posts, comments, router: rootReducer\n});\n\nexport default rootReducer;\n\n\n/*\nwhy are we using 'router: rootReducer'?\n   because we have 3 things in our state: posts, comments, and the changes of our URL as well \u003c--important to note. its not just posts, comments.\n*/\n```\n\nat this point, our app doesnt know anything at all about our store. lets change that.\nin `reduxtagram.js`:\n\n`import { Provider } from 'react-redux';`\n\nlets also import our store we created in addition to the history (IDK what 'history' does):\n\n`import store, { history } from './store';`\n\n^ so in this we can see the console.log()s firing off with objects filled with post and comment content, but we dont see actions for 'increment_likes' or 'add_comment' like declared in our 'actionCreators.js' file.\n\n** so above we've already imported our 'Provider', now we're going to use it as an element to expose our store to our app. we'll do this by wrapping `\u003cProvider\u003e` around our \u003cRouter\u003e component**:\n\n```\nconst router = (\n   \u003cProvider\u003e\n      \u003cRouter history={ BrowserHistory }\u003e\n         \u003cRoute path=\"/\" component={ Main }\u003e\n            \u003cIndexRoute component={ PhotoGrid }\u003e\u003c/IndexRoute\u003e\n            \u003cRoute path=\"/view/:postId\" component={ Single }\u003e\u003c/Route\u003e\n         \u003c/Route\u003e\n      \u003c/Router\u003e\n   \u003c/Provider\u003e\n)\n```\n\nthen we'll add a store prop:\n`\u003cProvider store={store}\u003e`\n\nnow lets replace the prop value 'BrowserHistory' in \u003cRouter\u003e to match 'history' that we've created in 'store.js':\n\n`\u003cRouter history={ history }\u003e`\n\nnow lets take a look at what we've done so far:\n\n```\nimport React from 'react';\nimport { render } from 'react-dom';\n\nimport css from './styles/style.styl';\n\nimport Main from './components/Main';\nimport PhotoGrid from './components/PhotoGrid';\nimport Single from './components/Single';\n\n//import react router deps\nimport { Router, Route, IndexRoute, BrowserHistory } from 'react-router';\nimport { Provider } from 'react-redux';\nimport store, { history } from './store';\n\nconst router = (\n   \u003cProvider store={store}\u003e\n      \u003cRouter history={ history }\u003e\n         \u003cRoute path=\"/\" component={ Main }\u003e\n            \u003cIndexRoute component={ PhotoGrid }\u003e\u003c/IndexRoute\u003e\n            \u003cRoute path=\"/view/:postId\" component={ Single }\u003e\u003c/Route\u003e\n         \u003c/Route\u003e\n      \u003c/Router\u003e\n   \u003c/Provider\u003e\n)\n\nrender(router, document.getElementById(\"root\"));\n```\n\nwe've imported the Provider, wrapped it around our Router, exposing our app to the redux state, then we had previously defined 'history' in `store.js` like this:\n\n```\n//create our history with the store to be exported (IDK what this method does)\nexport const history = syncHistoryWithStore(browserHistory, store);\n```\n\nSo now our store is exposed to our app and we can check it out via going to our 'react' tab in devtools, selecting \u003cProvider\u003e, then in the console:\n`$r.store.getState();`\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eUnderstanding the reducer's job and dispatching actions\u003c/summary\u003e\nreminder: action = what happened and where, reducer = the change that happens\n\n^ how do we hook them up together?\n\n* we dispatch an action, and the reducer listens for the action and then does something to handle that action.\n\n* action = the event, reducer = the listener\n\nrun this in your console: `$r.store.dispatch({ type: 'INCREMENT_LIKES', index: 0 });`\n\nthis occurs after:\n```\nposts.js:7 The post will change\nposts.js:8 []length: 0__proto__: Array(0) Object {type: \"INCREMENT_LIKES\", index: 0}\ncomments.js:2 []length: 0__proto__: Array(0) Object {type: \"INCREMENT_LIKES\", index: 0}\nObject {type: \"INCREMENT_LIKES\", index: 0}\n```\n\n^ as you see, both `posts.js` and `comments.js`: are fired off. THIS IS ESSENTIAL FOR UNDERSTANDING REDUX.\n\n* WHEN YOU DISPATCH AN ACTION, EVERY REDUCER RUNS. IF YOU WANT TO ACT UPON THAT ACTION, YOU HAVE TO DEFINE THAT IN THE REDUCER.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAccessing Dispatch and State with Redux\u003c/summary\u003e\nHow to populate data? In react, we typically pass state in at the top level and then pass it down to wherever it should go via props. In redux, we use \"connect\" to inject data at the component level we want it at, then pass it down if we want.\n\nif we look at our main.js file at the moment, it looks like this:\n```\nimport React from 'react';\nimport { Link } from 'react-router';\n\nconst Main = React.createClass({\n   render() {\n      return (\n         \u003cdiv\u003e\n            \u003ch1\u003e\n               \u003cLink to=\"/\"\u003eReduxstagram\u003c/Link\u003e\n            \u003c/h1\u003e\n            {React.cloneElement(this.props.children, this.props)};\n         \u003c/div\u003e\n      )\n   }\n});\n\nexport default Main;\n//this 'export' allows us to import the component in our reduxstagram file\n\n```\n\n^ this is a mostly presentational component containing mostly DOM markup, but we wanna infuse it with both the action creators as well as the data...we do this by creating a 2nd component that will sorta sprinkle this stuff on top.\n\nlets create a new file called 'App.js' inside the '/components' folder:\n```\nimport { bindActionCreators } from 'redux';\nimport { connect } from 'react-redux';\nimport * as actionCreators from '../actions/actionCreators';\nimport Main from './Main';\n\nconst App =\n```\n\nnow instead of using `React.createClass({})` for App, we will use connect():\n\n`const App = connect( mapStateToProps, mapDispatchToProps);`\n\n^ these are 2 functions which will take the state and our action creators and surface those in the app via props. now lets create those functions:\n\n```\nfunction mapStateToProps(state){\n   return {\n      posts: state.posts,\n      comment: state.comments\n   }\n}\n\nfunction mapDispatchToProps(dispatch){\n   return bindActionCreators(actionCreators, dispatch);\n}\n```\n^ these 2 functions will surface our data and make the dispatch actions possible.\n\nnow go back to reduxtagram.js:\n\ninstead of importing 'Main' directly like this:\n```\nimport Main from './components/Main';\n\n\u003cRoute path=\"/\" component={ Main }\u003e\n   \u003cIndexRoute component={ PhotoGrid }\u003e\u003c/IndexRoute\u003e\n   \u003cRoute path=\"/view/:postId\" component={ Single }\u003e\u003c/Route\u003e\n\u003c/Route\u003e\n```\n\nwe want to change 'Main' to 'App' since we'll be using that:\n\n```\nimport App from './components/App';\n\n\u003cRoute path=\"/\" component={ App }\u003e\n   \u003cIndexRoute component={ PhotoGrid }\u003e\u003c/IndexRoute\u003e\n   \u003cRoute path=\"/view/:postId\" component={ Single }\u003e\u003c/Route\u003e\n\u003c/Route\u003e\n```\n\n\nand now in App.js, we're going to attach the 'Main' component to the created functions via connect(). this looks strange, but I will explain:\n\n`const App = connect( mapStateToProps, mapDispatchToProps)(Main);`\n\n**Remember: we use 'connect'to attach these two functions to our 'Main' component. These 2 functions pass down the data and dispatch actions via props.**\n\na little backtrack but worth it:\n\nIn redux, all our state lives in a store. we make the store accessible via the 'react-redux' `\u003cprovider\u003e`, wrapping our component structure with it. now it is accessible, but not connected. In redux, We can only change state by dispatching an action, and we can only retrieve data by obtaining a store's current state. connect() allows us to do that.\n\nIt looks a bit weird because of the syntax (double parantheses), but makes sense. here's a fantastic read on connect():\nhttp://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eDisplaying Redux state inside our components\u003c/summary\u003e\n\nlets create Photo.js inside our /components directory:\n```\nimport React from 'react';\n\nconst Photo = React.createClass({\n   render() {\n      return (\n         \u003cfigure className=\"grid-figure\"\u003e\n            I am a photo!\n         \u003c/figure\u003e\n      )\n   }\n});\n\nexport default Photo;\n\n```\n\nnow lets map through the posts in our photoGrid, and for Each post, lets add a \u003cPhoto/\u003e component:\n```\n\u003cdiv className=\"photo-grid\"\u003e\n   { this.props.posts.map((post, i) =\u003e \u003cPhoto /\u003e)}\n\u003c/div\u003e\n```\n\n^ looks good, but doesnt inherit any props, which it needs to display photos. lets do that:\n\n`{ this.props.posts.map((post, i) =\u003e \u003cPhoto {...this.props} /\u003e)}`\n\nwe also need to give it a key because each child should have a unique key...react needs a key to differentiate which photo is which. so we'll add that using the index 'i' as the unique key:\n\n```\n{ this.props.posts.map((post, i) =\u003e \u003cPhoto {...this.props} key={i} /\u003e)}\n```\n\nnow we also need to pass the index value that goes along with it:\n```\n{ this.props.posts.map((post, i) =\u003e \u003cPhoto {...this.props} key={i} i={ i }/\u003e)}\n```\n\n**we cant use 'key' as our unique ID because react reserves 'key' for itself. if you want a unique ID, you'll have to use the index.**\n\nnow lets add the post prop: `{ this.props.posts.map((post, i) =\u003e \u003cPhoto {...this.props} key={i} i={ i } post={ post }/\u003e)}`\n\nnow we have our unique identifier and 'post' props. lets create a proper `\u003cPhoto\u003e` component in `photo.js`:\n```\nimport React from 'react';\nimport { Link } from 'react-router';\n\nconst Photo = React.createClass({\n   render() {\n      const { post, i, comments } = this.props;\n\n      return (\n         \u003cfigure className=\"grid-figure\"\u003e\n            \u003cdiv className=\"grid-photo-wrap\"\u003e\n               \u003cLink to={`/view/${ post.code }`}\u003e\n               \u003cimg src={post.display_src} alt={post.caption} className=\"grid-photo\" /\u003e\n                { post.caption }\n               \u003c/Link\u003e\n            \u003c/div\u003e\n         \u003c/figure\u003e\n      )\n   }\n});\n\nexport default Photo;\n```\n^ we give individual links to each Photo component using the native \u003cLink\u003e tag provided by importing it from 'react-router'..we then use some variables to shorten our props declarations so we dont constantly use 'this.props' all the time.\n\nThese link urls go to urls based on the `\u003cSingle\u003e` component we defined in `main.js`\n\n# Updating our state with reducers\n\nwhen someone clicks a 'like' button, we want the 'increment' function to run. we've already included the 'increment' function via props, so we can pass it in a click handler:\n\nin photo.js\n```\n\u003cbutton onClick={this.props.increment()} className=\"likes\"\u003e\u0026hearts; {post.likes}\u003c/button\u003e\n```\n\n^ and when clicking, we see that the event is being fired off. nice, but we need to pass in its unique identifier:\n\n`onClick={this.props.increment.bind(null, i)}`\n\n**if necessary, refer to notes on bind()**\n\nnow when we click, ALL REDUCERS show up in console.\n\n### An Aside about pure/impure functions:\nredux is designed in a functional programming style, meaning no impure functions. impure functions are functions that have side effects and affect things not within itself. take this for example:\n```\nfunction addLike(picture) {\n   picture.likes++;\n   console.log(picture);\n   return picture\n}\n\nvar post = { name: \"A cool picture\", likes: 10};\n\naddLike(post); //picture.likes is 11\naddLike(post); //picture.likes is 12\naddLike(post); //picture.likes is 13\n\n```\n\n^ NOT GOOD! this is because we are changing what is inside of the 'post' object. lets instead create a copy of the post object and return that new copy instead, keeping the original 'post' object intact:\n```\nfunction addLike(picture) {\n   //take a copy\n   var pic = Object.assign({}, picture); //creates a new Object from 'picture'\n   pic.likes++;\n   console.log(pic);\n   return pic\n}\n\nvar post = { name: \"A cool picture\", likes: 10};\n\naddLike(post); //picture.likes is 11\naddLike(post); //picture.likes is 11\naddLike(post); //picture.likes is 11\n```\n\nNow since we understand the need for pure functions in redux, lets go back and code what goes in our reducer for 'INCREMENT_LIKES' action, creating a switch statement to do something if it is the INCREMENT_LIKES action dispatcher:\n\n```\nfunction posts(state =[], action) {\n   switch(action.type) {\n      case 'INCREMENT_LIKES' :\n         const i = action.index;\n         return [\n            ...state.slice(0, i), //before the one we are updating\n            {...state[i], likes: state[i].likes + 1}\n            ...state.slice(i + 1), //after the one we are updating\n         ]\n      // return the updated state\n      default:\n         return state;\n   }\n}\n```\n\n^ we're returning a copy of the new state array, which includes all the same stuff EXCEPT it updates the specific state index's 'likes' object to be incremented by 1. One thing I've learned a lot about react and redux is the need to create copies of state to replace the state them rather than just updating the current state. seems cleaner.\n\n# Displaying the single component\n\nlets update our Comment component located inside 'Single.js' to have some usable props:\n```\nimport React from 'react';\nimport Photo from './Photo';\nimport Comments from './Comments';\n\nconst Single = React.createClass({\n   render() {\n\n      const {postId} = this.props.params;\n\n      //we need the index of the post (we get through finding the specific url code then the index)...\n      const i = this.props.posts.findIndex((post) =\u003e post.code === postId);\n\n      //the post itself, found with 'i'\n      const post = this.props.posts[i];\n      console.log(post);\n\n      const postComments = this.props.comments[postId] || [];\n\n      return (\n         \u003cdiv className=\"single-photo\"\u003e\n            \u003cPhoto i={i} post={post} {...this.props} /\u003e\n            \u003cComments postComments={postComments} /\u003e\n         \u003c/div\u003e\n      )\n   }\n});\n\nexport default Single;\n```\n\nnow in the \u003cComments\u003e component file, we'll iterate and use the comments using `map()`:\n\n```\nimport React from 'react';\nimport Photo from './Photo';\n\nconst Comments = React.createClass({\n   renderComment(comment, i) {\n      return (\n         \u003cdiv className=\"comment\" key={i}\u003e\n            \u003cp\u003e\n               \u003cstrong\u003e{comment.user}\u003c/strong\u003e\n               {comment.text}\n               \u003cbutton className=\"remove-comment\"\u003e\u0026times;\u003c/button\u003e\n            \u003c/p\u003e\n         \u003c/div\u003e\n      )\n   },\n\n   render() {\n      return (\n         \u003cdiv className=\"comment\"\u003e\n            {this.props.postComments.map(this.renderComment)}\n         \u003c/div\u003e\n      )\n   }\n});\n\nexport default Comments;\n```\n\n^ this populates all photo comments to the DOM, now lets make a form element like on the reduxtagram demo where people can add their own comments.\n\n```\n   render() {\n      return (\n         \u003cdiv className=\"comments\"\u003e\n            {this.props.postComments.map(this.renderComment)}\n            \u003cform ref=\"commentForm\" className=\"comment-form\"\u003e\n               \u003cinput type=\"text\" ref=\"author\" placeholder=\"author\" /\u003e\n               \u003cinput type=\"text\" ref=\"comment\" placeholder=\"comment\" /\u003e\n               \u003cinput type=\"submit\" hidden /\u003e\n         \u003c/div\u003e\n      )\n   }\n```\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eUpdating comment state in our store\u003c/summary\u003e\n\nso on submitting a new comment, we just refresh the page. lets handle that submit so new comments can be added.\n```\n   handleSubmit(e) {\n      e.preventDefault();\n      const { postId } = this.props.params;\n      const author = this.refs.author.value;\n      const comment = this.refs.comment.value;\n      console.log(postId, author, comment);\n   },\n\n   render() {\n      return (\n         \u003cdiv className=\"comments\"\u003e\n            {this.props.postComments.map(this.renderComment)}\n            \u003cform ref=\"commentForm\" className=\"comment-form\" onSubmit={this.handleSubmit}\u003e\n               \u003cinput type=\"text\" ref=\"author\" placeholder=\"author\" /\u003e\n               \u003cinput type=\"text\" ref=\"comment\" placeholder=\"comment\" /\u003e\n               \u003cinput type=\"submit\" hidden /\u003e\n            \u003c/form\u003e\n         \u003c/div\u003e\n      )\n   }\n```\n\nREMEMBER: in react, we get values from inputs using the 'ref' attribute. so we've got the author, comment but not the specific post, as the error console is stating it isnt defined. we need to pass those params (postId) in as props on the `\u003cComment\u003e` component used in `single.js`:\n\n```\nreturn (\n   \u003cdiv className=\"single-photo\"\u003e\n      \u003cPhoto i={i} post={post} {...this.props} /\u003e\n      \u003cComments postComments={postComments} {...this.props}/\u003e\n   \u003c/div\u003e\n)\n```\n\nso now our console.log() is firing off nicely. lets take these form comments and update our state using a REDUCER. we'll add some code to `actionCreators.js` to handle updating the state. we'll use the already created `addComment()` function within `handleSubmit()`.\n\n```\n   handleSubmit(e) {\n      e.preventDefault();\n      const { postId } = this.props.params;\n      const author = this.refs.author.value;\n      const comment = this.refs.comment.value;\n      console.log(postId, author, comment);\n      this.props.addComment(postId, author, comment);\n   },\n```\n\nwe'll run a quick `console.log()` statement in actionCreators.js to make sure things are working nicely:\n```\nexport function addComment(postId, author, comment) {\n   console.log('dispatching add comment');\n   return {\n      type: 'ADD_COMMENT',\n      postId,\n      author,\n      comment\n   }\n}\n```\nNow its working, our action is being dispatched. now we should actually update the state. so lets review this real quick:\n\n1. we use 'ref' attributes to get values from text inputs\n2. we've passed along all props from the \u003csingle\u003e component to the \u003ccomment\u003e component via {...this.props}. our props also include our reducer functions like addComment() removeComment() incremementLikes() and so forth.\n4. we use the addComment() function in our handleSubmit() function:\n\n`this.props.addComment(postId, author, comment);`\n\n5. we added a console.log() statement to check and make sure the action is being dispatched, and it is.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eRedux Reducer Composition\u003c/summary\u003e\nour 'comments' state is a big object with all comments, with each item being a key based on the unique id of the associated post and an array with its comments.\n\nwhen we want to add a comment we dont have to update the entire 'comments' state, we just want to update that one little piece.\n\n***reducer composition = updating just a slice of state.***\n\nwe've created reducers for posts, comments, and now we'll do a 'sub-reducer' for single comments within a post, in comments.js:\n\n```\nfunction postComments(state =[], action) {\n   switch(action.type) {\n      case 'ADD_COMMENT':\n      //return the new state with the new comment\n      return [...state, {\n         user: action.author,\n         text: action.comment,\n      }];\n\n      case 'REMOVE_COMMENT':\n         return state;\n      default:\n         return state;\n   }\n   return state;\n}\n\n\nfunction comments(state =[], action) {\n   if(typeof action.postId !== 'undefined') {\n         console.log(action);\n\n      return {\n         //take the current state\n         ...state,\n         //overwrite this post with the new one\n         //\n         [action.postId] : postComments(state[action.postId], action)\n      }\n   }\n   return state;\n}\n\nexport default comments;\n```\n^ what we're doing here:\n\n1. we create a switch statement in postComments() where if the action is 'ADD_COMMENT' we return the new state with the new comment.\n2. in comments(), we make sure the action.postId is not undefined, and if its not, we return the current state and in the specific post ID, we handle the updating of the post via the postComments() function.\n\n**so we create different functions to handle different things. one function to update the specific post ID comment state and one function to return the overall state with the newly updated postID comment state inside it. this is why postComments() is considered a sub-reducer, because it is used as a reducer within a reducer.**\n\nso we're good on adding a comment, lets handle removing a comment as well:\n\nin Comments.js:\n```\nfunction postComments(state =[], action) {\n   switch(action.type) {\n      case 'ADD_COMMENT':\n      //return the new state with the new comment\n      return [...state, {\n         user: action.author,\n         text: action.comment,\n      }];\n\n      case 'REMOVE_COMMENT':\n         console.log(\"removing a comment\");\n         return state;\n      default:\n         return state;\n   }\n   return state;\n}\n```\nlets hook this up to be fired off in our remove-comment button via a click handler:\n\n```\n   renderComment(comment, i) {\n      return (\n         \u003cdiv className=\"comment\" key={i}\u003e\n            \u003cp\u003e\n               \u003cstrong\u003e{comment.user}\u003c/strong\u003e\n               {comment.text}\n               \u003cbutton className=\"remove-comment\" onClick={this.props.removeComment()}\u003e\u0026times;\u003c/button\u003e\n            \u003c/p\u003e\n         \u003c/div\u003e\n      )\n   },\n```\n\nThe `removeComment()` function needs 2 args: a postId and the unique index of the comment we're removing. also we'll use `bind()` to make sure we always get the right instance of the unique index:\n\n```\n\u003cbutton className=\"remove-comment\" onClick={this.props.removeComment.bind(null, this.props.params.postId, i)}\u003e\u0026times;\u003c/button\u003e\n```\n\n^ this works, but now we should actually remove the comment instead of simply running a `console.log()`. what we'll do is return a copy of the comment state without the specific comment, using `slice()` to do so:\n\n```\n case 'REMOVE_COMMENT':\n   //we need to return the new state without the deleted comment\n   return [\n   // from the start to the one we want to delete\n   ...state.slice(0, action.i)]\n   // after the deleted one to the end\n   ...state.slice(action.i + 1)\n   return state;\n```\nso its less of us removing the state by deleting it and its more of identifying the actual index and returning the state without it.\n\nanother example: `[\"wow\",\"neat\",\"cool\",\"nice\"];`\n\nwe wanna delete the 3rd one. we return wow and neat, skip cool, and return nice too:\n\n`[\"wow\",\"neat\",\"nice\"];`\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eHot Reloading Redux Reducers with webpack\u003c/summary\u003e\n\nwe can do live reload in our jsx fine, but if we want to change our reducer (ex: we want to change -click = 1 like to click = 10 likes) we have to do some additional work:\n\nstore.js:\n```\n//allows hot reload by checking to see if module has changed first then...something?\nif (module.hot) {\n   module.hot.accept('./reducers/', () =\u003e {\n      const nextRootReducer = require('./reducers/index').default;\n      store.replaceReducer(nextRootReducer);\n   });\n}\n```\n^ definitely worth a review\n\u003c/details\u003e\n\u003cdetails\u003e\n\u003csummary\u003eRedux DevTools\u003c/summary\u003e\nredux devTools' 'sweep' tab literally logs every action that is fired off and displays the state, action, etc...if you want to go 'back in time' to say, debug something, you can simply click on that action to toggle it to remove it from happening in the UI.\n\nan abridged description of Redux DevTools tabs:\n===\n* sweep = remove any actions from your UI/log\n* commit = works similar to git commit...adds any change to be the initial state\n* revert = reverts anything since your last commit\n* reset = brings all actions (including commits) all the way back to initial state\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcorral%2Fredux-course","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftcorral%2Fredux-course","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcorral%2Fredux-course/lists"}