|
@@ -1,3 +1,7 @@
|
|
|
+# Learn Redux
|
|
|
+* <https://learnredux.com/account>
|
|
|
+* <http://redux.js.org/docs/introduction/>
|
|
|
+
|
|
|
## Video 1: Setting up Webpack
|
|
|
* React Dev Tools
|
|
|
* Redux Dev Tools
|
|
@@ -6,28 +10,31 @@
|
|
|
|
|
|
## Video 3: Create Single and PhotoGrid components
|
|
|
|
|
|
-## Video 3: Setting up React Router
|
|
|
-```javascript
|
|
|
+## Video 4: Setting up React Router
|
|
|
+
|
|
|
+```jsx
|
|
|
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
|
|
|
|
|
|
const router = (
|
|
|
<Router history={browserHistory}>
|
|
|
<Route path='/' component={Main}>
|
|
|
- <IndexRoute component={} \/>
|
|
|
- <Route path='/view/:postId' \/>
|
|
|
- <\/Route>
|
|
|
- <\/Router>
|
|
|
+ <IndexRoute component={} />
|
|
|
+ <Route path='/view/:postId' />
|
|
|
+ </Route>
|
|
|
+ </Router>
|
|
|
)
|
|
|
|
|
|
render(router, document.getElementById('root'))
|
|
|
```
|
|
|
+
|
|
|
* Use `<Link to=''>Bla</Link>` to create push state links.
|
|
|
* browserHistory will be replaced later by `history` which first syncs the browser history with the react store (see next video).
|
|
|
|
|
|
-## Video 4: Setting up React Store
|
|
|
+## Video 5: Setting up React Store
|
|
|
* One giant state for all data
|
|
|
* it is called `store`
|
|
|
-```javascript
|
|
|
+
|
|
|
+```jsx
|
|
|
import { createStore, compose } from 'redux'
|
|
|
import { syncHistoryWithStore } from 'react-router-redux'
|
|
|
import { browserHistory } from 'react-router'
|
|
@@ -39,11 +46,12 @@ export const history = syncHistoryWithStore(browserHistory, store)
|
|
|
export default store
|
|
|
```
|
|
|
|
|
|
-## Video 5: Redux Actions
|
|
|
+## Video 6: Redux Actions
|
|
|
* actions invoke changes of the state (adding, updating, removing data)
|
|
|
* actions are objects with two elements: type and payload
|
|
|
* action creators are functions which return actions:
|
|
|
-```javascript
|
|
|
+
|
|
|
+```jsx
|
|
|
function increment(index) {
|
|
|
return {
|
|
|
type: 'INCREMENT_LIKES',
|
|
@@ -60,7 +68,7 @@ function addComment(postId, author, comment) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-## Video 6: Redux Reducers
|
|
|
+## Video 7: Redux Reducers
|
|
|
* reducers handle the data when an action is received
|
|
|
* use one reducer per components
|
|
|
* reducers are functions which take
|
|
@@ -68,7 +76,8 @@ function addComment(postId, author, comment) {
|
|
|
- a copy of the current state
|
|
|
* reducers return the updated state if there is a change, or the previous state
|
|
|
* always start like this and then add some conditional functionality:
|
|
|
-```javascript
|
|
|
+
|
|
|
+```jsx
|
|
|
function posts(state = [], action) {
|
|
|
if (...) {
|
|
|
return nextState
|
|
@@ -78,8 +87,10 @@ function posts(state = [], action) {
|
|
|
|
|
|
export default posts
|
|
|
```
|
|
|
+
|
|
|
* combine them in a root reducer
|
|
|
-```javascript
|
|
|
+
|
|
|
+```jsx
|
|
|
import { combineReducers } from 'redux'
|
|
|
import { routerReducer } from 'react-router-redux'
|
|
|
|
|
@@ -87,75 +98,177 @@ import posts from './posts'
|
|
|
const rootReducer = combineReducers({ posts, ..., routing: routerReducer })
|
|
|
export default rootReducer
|
|
|
```
|
|
|
+
|
|
|
* always add `routing: routerReducer` at the end to integrate the route changes with other action changes.
|
|
|
* executes URL changes implicitely
|
|
|
* <https://github.com/reactjs/react-router-redux>
|
|
|
|
|
|
-## Video 7: Integrating our Store with React Router
|
|
|
+## Video 8: Integrating our Store with React Router
|
|
|
* to make the store globally available, put it on top of the routing
|
|
|
* replace the browserHistory as mentioned before.
|
|
|
-```diff
|
|
|
-import { Router, Route, IndexRoute, browserHistory } from 'react-router'
|
|
|
-+import { Provider } from 'react-redux'
|
|
|
-+import store, { history } from './store'
|
|
|
+ ```diff
|
|
|
+ import { Router, Route, IndexRoute, browserHistory } from 'react-router'
|
|
|
+ +import { Provider } from 'react-redux'
|
|
|
+ +import store, { history } from './store'
|
|
|
|
|
|
-const router = (
|
|
|
-+ <Provider store={store}>
|
|
|
-- <Router history={browserHistory}>
|
|
|
-+ <Router history={history}>
|
|
|
- <Route path='/' component={Main}>
|
|
|
- <IndexRoute component={} \/>
|
|
|
- <Route path='/view/:postId' \/>
|
|
|
- <\/Route>
|
|
|
- <\/Router>
|
|
|
-+ </Provider>
|
|
|
-)
|
|
|
+ const router = (
|
|
|
+ + <Provider store={store}>
|
|
|
+ - <Router history={browserHistory}>
|
|
|
+ + <Router history={history}>
|
|
|
+ <Route path='/' component={Main}>
|
|
|
+ <IndexRoute component={} \/>
|
|
|
+ <Route path='/view/:postId' \/>
|
|
|
+ <\/Route>
|
|
|
+ <\/Router>
|
|
|
+ + </Provider>
|
|
|
+ )
|
|
|
|
|
|
-render(router, document.getElementById('root'))
|
|
|
-```
|
|
|
+ render(router, document.getElementById('root'))
|
|
|
+ ```
|
|
|
* The state is now stored in the store. `$r.store.getState()`
|
|
|
|
|
|
-## Video 8: Understanding the Reducer's Job and Dispatching Actions
|
|
|
+## Video 9: Understanding the Reducer's Job and Dispatching Actions
|
|
|
* action creators return actions
|
|
|
* actions invoke state changes
|
|
|
* reducers change the state
|
|
|
* the store provides a dispatch function
|
|
|
-```javascript
|
|
|
-// on the console
|
|
|
-$r.store.dispatch({type: 'INCREMENT', index: 1})
|
|
|
-```
|
|
|
+
|
|
|
+ ```jsx
|
|
|
+ // on the console
|
|
|
+ $r.store.dispatch({type: 'INCREMENT', index: 1})
|
|
|
+ ```
|
|
|
* The dispatcher sends the action to **every** reducer
|
|
|
* Therefore, the reducer needs to have some conditional logic.
|
|
|
|
|
|
-## Video 9: Accessing dispatch and state with Redux
|
|
|
+## Video 10: Accessing dispatch and state with Redux
|
|
|
* in regular React, the state or parts of it are propagated via props
|
|
|
* in Redux, `connect` injects the data in the components that need it
|
|
|
* for that, we wrap the main component with one that has the state and dispatcher propagated
|
|
|
-```javascript
|
|
|
-import { bindActionCreators } from 'redux'
|
|
|
-import { connect } from 'react-redux'
|
|
|
-import * as actionCreators from './actions/actionCreators'
|
|
|
-import Main from './Main'
|
|
|
+
|
|
|
+ ```jsx
|
|
|
+ import { bindActionCreators } from 'redux'
|
|
|
+ import { connect } from 'react-redux'
|
|
|
+ import * as actionCreators from './actions/actionCreators'
|
|
|
+ import Main from './Main'
|
|
|
|
|
|
-function mapStateToProps(state) {
|
|
|
- return {
|
|
|
- posts: state.posts,
|
|
|
- comments: state.comments,
|
|
|
+ function mapStateToProps(state) {
|
|
|
+ return {
|
|
|
+ posts: state.posts,
|
|
|
+ comments: state.comments,
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-function mapDispatchToProps(dispatch) {
|
|
|
- return bindActionCreators(actionCreators, dispatch)
|
|
|
-}
|
|
|
+ function mapDispatchToProps(dispatch) {
|
|
|
+ return bindActionCreators(actionCreators, dispatch)
|
|
|
+ }
|
|
|
|
|
|
-const App = connect(mapStateToProps, mapDispatchToProps)(Main)
|
|
|
-export default App
|
|
|
-```
|
|
|
+ const App = connect(mapStateToProps, mapDispatchToProps)(Main)
|
|
|
+ export default App
|
|
|
+ ```
|
|
|
* because Main ist just JSX, connect injects standardized props into it and makes the state and dispatcher available.
|
|
|
* You can still propagate data down with props
|
|
|
|
|
|
-## Video 10: Displaying Redux State inside our Components
|
|
|
+## Video 11: Displaying Redux State inside our Components
|
|
|
* Creating React components
|
|
|
|
|
|
-## Video 11: Updating State with Reducers
|
|
|
+## Video 12: Updating State with Reducers
|
|
|
+* Once you propagate the reducer function to the component, this is how you pass arguments to the function call
|
|
|
+ - Remember that `<button onClick={this.props.function(argument)} ...>` would call the function at document load!
|
|
|
+
|
|
|
+ ```html
|
|
|
+ <button onClick={this.props.function.bind(null, argument)} ...>
|
|
|
+ ```
|
|
|
+
|
|
|
+* Use pure functions to manipulate state. So the same inputs (previous state, action) always gives the same output (next state).
|
|
|
+ - Makes testing easier.
|
|
|
+ - Enables Redux dev tools, time travel etc.
|
|
|
+* Do the same as in React state handling: Copy, modify, return copy:
|
|
|
+
|
|
|
+ ```jsx
|
|
|
+ function posts(state = [], action) {
|
|
|
+ switch(action.type) {
|
|
|
+ const i = action.index
|
|
|
+ case 'INCREMENT_LIKES' :
|
|
|
+ return [
|
|
|
+ ...state.slice(0, i),
|
|
|
+ { ...state[i], likes: state[i].likes + 1 },
|
|
|
+ ...state.slice(i + 1)
|
|
|
+ ]
|
|
|
+ default:
|
|
|
+ return state
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+## Video 13: Displaying the Single Photo Component
|
|
|
+* Polyfills are new ES6 functions cross compiled for old browsers through Babel. [Example for findIndex()]
|
|
|
+* You can use `findIndex()` to get a certain element of an array
|
|
|
+
|
|
|
+ ```jsx
|
|
|
+ render () {
|
|
|
+ const { postId } = this.props.params
|
|
|
+ const i = this.props.posts.findIndex((post) => post.code === postId)
|
|
|
+ const post = this.props.posts[i]
|
|
|
+ const comments = this.props.comments[postId] || []
|
|
|
+ ...
|
|
|
+ }
|
|
|
+ ```
|
|
|
+[Example for findIndex()]: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex#Polyfill
|
|
|
+
|
|
|
+## Video 14: Displaying and Adding Comments
|
|
|
+* You can hide a submit button, if it's clear that the Return key submits the form.
|
|
|
+* consider separate render functions for looped-over elements to keep the functions clearer.
|
|
|
+
|
|
|
+## Video 15: Updating Comment State in our Store
|
|
|
+* Write a functions, which handles the submit of a form
|
|
|
+
|
|
|
+```jsx
|
|
|
+...
|
|
|
+handleSubmit(e) {
|
|
|
+ e.preventDefault()
|
|
|
+ const { postId } = this.props.params
|
|
|
+ const author = this.refs.author.value
|
|
|
+ this.props.addComment(postId, author, ...)
|
|
|
+}
|
|
|
+render() {
|
|
|
+ ...
|
|
|
+ <form ref='commentForm' className='comment-form' onSubmit={this.handleSubmit}>
|
|
|
+ <input type='text' ref='author' placeholder='author'/>
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## Video 16: Redux Reducer Composition
|
|
|
+* Just like you can split up your state in different pieces, you can also split up the reducers. This is called reducer composition
|
|
|
+* Example for comments:
|
|
|
+ - write one function to handle all comments
|
|
|
+ - write other functions to only handle one comment at a time
|
|
|
+
|
|
|
+ ```jsx
|
|
|
+ function postComments(state = [], action) {
|
|
|
+ switch (action.type) {
|
|
|
+ case 'ADD_COMMENT':
|
|
|
+ return [
|
|
|
+ ...state, {
|
|
|
+ user: action.author,
|
|
|
+ text: action.comment,
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ default:
|
|
|
+ return state
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function comments(state = [], action) {
|
|
|
+ if (typeof action.postId !== 'undefined') {
|
|
|
+ return {
|
|
|
+ ...state,
|
|
|
+ [action.postId]: postComments(state[action.postId], action)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return state
|
|
|
+ }
|
|
|
+
|
|
|
+ export default comment
|
|
|
+ ```
|
|
|
|
|
|
+## Video 18: Hot Reloading Redux Reducers with Webpack
|