diff --git a/learn-redux/client/actions/actionCreators.js b/learn-redux/client/actions/actionCreators.js new file mode 100644 index 000000000..b050c69fa --- /dev/null +++ b/learn-redux/client/actions/actionCreators.js @@ -0,0 +1,28 @@ +// Creates actions including what happened and the payload of information that is needed to execute. When the actions get dispatched they are handled by a reducer and then the reducer is responsible for updating the state. + +// increment +export function increment(index) { + return { + type: 'INCREMENT_LIKES', + index: index + } +} + +// add commnet +export function addComment(postId, author, comment) { + return { + type: 'ADD_COMMENT', + postId: postId, + author: author, + comment: comment + } +} + +// remove comment +export function removeComment(postId, i) { + return { + type: 'REMOVE_COMMENT', + i: i, + postId: postId + } +} diff --git a/learn-redux/client/components/Comments.js b/learn-redux/client/components/Comments.js new file mode 100644 index 000000000..71f919044 --- /dev/null +++ b/learn-redux/client/components/Comments.js @@ -0,0 +1,37 @@ +import React from 'react'; + +const Comments = React.createClass({ + renderComment(comment, i) { + return ( +
+

+ {comment.user} + {comment.text} + +

+
+ ) + }, + handleSubmit(e) { + e.preventDefault(); + const { postId } = this.props.params; + const author = this.refs.author.value; + const comment = this.refs.comment.value; + this.props.addComment(postId, author, comment); + this.refs.commentForm.reset(); + }, + render() { + return ( +
+ {this.props.postComments.map(this.renderComment)} +
+ + + +
+
+ ) + } +}) + +export default Comments; diff --git a/learn-redux/client/components/Main.js b/learn-redux/client/components/Main.js new file mode 100644 index 000000000..7d27f1516 --- /dev/null +++ b/learn-redux/client/components/Main.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { Link } from 'react-router'; + +const Main = React.createClass({ + render() { + return ( +
+

+ Reduxstagram +

+ {React.cloneElement(this.props.children, this.props)} + // Takes the props of the parent componenet, clones them and then passes them down to the child components. +
+ ) + } +}); + +export default Main; diff --git a/learn-redux/client/components/Photo.js b/learn-redux/client/components/Photo.js new file mode 100644 index 000000000..f44903734 --- /dev/null +++ b/learn-redux/client/components/Photo.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Link } from 'react-router'; +import CSSTransitionGroup from 'react-addons-css-transition-group'; + +const Photo = React.createClass({ + render() { + const { post, i, comments } = this.props; + return ( +
+
+ + {post.caption} + + + + + {post.likes} + + +
+ +
+

{post.caption}

+
+ + + + + {comments[post.code] ? comments[post.code].length : 0} + + +
+
+
+ ) + } +}) + +export default Photo diff --git a/learn-redux/client/components/PhotoGrid.js b/learn-redux/client/components/PhotoGrid.js new file mode 100644 index 000000000..29dd20dad --- /dev/null +++ b/learn-redux/client/components/PhotoGrid.js @@ -0,0 +1,14 @@ +import React from 'react'; +import Photo from './Photo'; + +const PhotoGrid = React.createClass({ + render() { + return ( +
+ {this.props.posts.map((post, i) => )} +
+ ) + } +}); + +export default PhotoGrid; diff --git a/learn-redux/client/components/Single.js b/learn-redux/client/components/Single.js new file mode 100644 index 000000000..d5d98808d --- /dev/null +++ b/learn-redux/client/components/Single.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { Link } from 'react-router'; +import Photo from './Photo'; +import Comments from './Comments'; + +const Single = React.createClass({ + render() { + //index of the post + const { postId } = this.props.params; + const i = this.props.posts.findIndex((post) => post.code === postId); + // get us the post + const post = this.props.posts[i]; + const postComments = this.props.comments[postId] || []; + + return ( +
+ + +
+ ) + } +}); + +export default Single; diff --git a/learn-redux/client/components/app.js b/learn-redux/client/components/app.js new file mode 100644 index 000000000..4b7cbe72c --- /dev/null +++ b/learn-redux/client/components/app.js @@ -0,0 +1,19 @@ +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 mapDispatchToProps(dispatch) { + return bindActionCreators(actionCreators, dispatch); +} + +const App = connect(mapStateToProps, mapDispatchToProps)(Main); + +export default App; diff --git a/learn-redux/client/reducers/comments.js b/learn-redux/client/reducers/comments.js new file mode 100644 index 000000000..64d38ce19 --- /dev/null +++ b/learn-redux/client/reducers/comments.js @@ -0,0 +1,32 @@ +function postComments(state = [], action) { + switch(action.type){ + case 'ADD_COMMENT': + // return the new state with the new comment + return [...state, { + user: action.author, + text: action.comment + }] + case 'REMOVE_COMMENT': + return [ + ...state.slice(0, action.i), + ...state.slice(action.i + 1) + ] + default: + return state; + } +} + +function comments(state = [], action) { + if(typeof action.postId !== 'undefined') { + return { + // take the current state + ...state, + // overwrite this post with new one + [action.postId]: postComments(state[action.postId], action) + } + } + return state; +} + + +export default comments; diff --git a/learn-redux/client/reducers/index.js b/learn-redux/client/reducers/index.js new file mode 100644 index 000000000..b6e2bb510 --- /dev/null +++ b/learn-redux/client/reducers/index.js @@ -0,0 +1,9 @@ +import { combineReducers } from 'redux'; +import { routerReducer } from 'react-router-redux'; + +import posts from './posts'; +import comments from './comments'; +// combines all reducers from reducers folder and prepare for export +const rootReducer = combineReducers({posts, comments, routing: routerReducer}); + +export default rootReducer; diff --git a/learn-redux/client/reducers/posts.js b/learn-redux/client/reducers/posts.js new file mode 100644 index 000000000..298fbb253 --- /dev/null +++ b/learn-redux/client/reducers/posts.js @@ -0,0 +1,22 @@ +// A reducer's job is to take in action and store, process the action and then return the store. + +// A reducer takes in two things: +// 1) the action (info about what happened) +// 2) copy of current state + +function posts(state = [], action) { + switch(action.type) { + case 'INCREMENT_LIKES' : + const i = action.index; + return [ + ...state.slice(0,i), // before the one we are updating + {...state[i], likes: state[i].likes + 1}, + ...state.slice(i + 1) // after the one we are updating + ] + // always have default returning the updated state + default: + return state; + } +} + +export default posts; diff --git a/learn-redux/client/reduxstagram.js b/learn-redux/client/reduxstagram.js index 598ea1874..e754338d7 100644 --- a/learn-redux/client/reduxstagram.js +++ b/learn-redux/client/reduxstagram.js @@ -1 +1,31 @@ -// let's go! +import React from 'react'; +import { render } from 'react-dom'; + +// Import css +import css from './styles/style.styl'; + +// Import Components +import App from './components/App'; +import Single from './components/Single'; +import PhotoGrid from './components/PhotoGrid'; + +// import react router dependecies +import { Router, Route, IndexRoute, browserHistory } from 'react-router'; +import { Provider } from 'react-redux'; +import store, { history } from './store'; + +const router = ( + + + // Router componenent needs a history object, which here is browserHistory which allows you to do push state without having to reload the page. + + // If the url matches "/" or a further extension of, grab the Main component. + + + // Then depending on the URL structure, either pass 'Main', 'PhotoGrid' or 'Single'. + + + +) + +render(router, document.getElementById('root')); diff --git a/learn-redux/client/store.js b/learn-redux/client/store.js new file mode 100644 index 000000000..b225d60a6 --- /dev/null +++ b/learn-redux/client/store.js @@ -0,0 +1,50 @@ +import { createStore, compose } from 'redux'; +import { syncHistoryWithStore } from 'react-router-redux'; +import { browserHistory } from 'react-router'; + +//import the root reducer +import rootReducer from './reducers/index'; // rootReducer is just a hub for importing all the individual reducers and exporting them as a whole. Syntax => const rootReducer = combineReducers({posts, comments, routing: routerReducer}); + +import comments from './data/comments'; // importing the initial data +import posts from './data/posts'; // importing the initial data + +// Passing initial data to defaultState +const defaultState = { + posts: posts, + comments: comments +}; + +// ________________________________________________ +// SUGAR ------> INSTALL REDUX DEV TOOLS. +// Install Redux dev tools into our store by using a store enhancer. This enables Redux Dev Tools in chrome to recognise your store. + const enhancers = compose( + // compose infuses our store with any of the enhancers we want. + window.devToolsExtension ? window.devToolsExtension() : f => f + // If dev tools is in the window, install it. If not just return the store. +); +// Then pass createStore the enhancers when creating the store below. +// ________________________________________________ + + +// Create the store using collection of reducers and collection of data +const store = createStore(rootReducer, defaultState, enhancers); + +export const history = syncHistoryWithStore(browserHistory, store); + + +// ________________________________________________ +// SUGAR ------> HOT RELOAD REDUCERS. Done by accepting the hot reload & then re-requiring the reducer. +if(module.hot) { + // First we check if the module is hot + module.hot.accept('./reducers/', () => { + // If it is hot, we accept it and run a function that is going to re-require and swap out the module for us. + const nextRootReducer = require('./reducers/index').default; + // Grab the main reducer (which is the top level index one). Use require because you cannot use an ES6 import statement inside of a function, must be done at top level. + store.replaceReducer(nextRootReducer) + // Finally we just replace the entire reducer with store.replaceReducer() and pass it the nextRootReducer. + }); +} +// ________________________________________________ + +// Export the newly created store +export default store;