├── .babelrc ├── .gitignore ├── README.md ├── dev ├── js │ ├── actions │ │ ├── README.md │ │ └── index.js │ ├── components │ │ ├── App.js │ │ └── README.md │ ├── containers │ │ ├── README.md │ │ ├── user-detail.js │ │ └── user-list.js │ ├── index.js │ └── reducers │ │ ├── README.md │ │ ├── index.js │ │ ├── reducer-active-user.js │ │ └── reducer-users.js └── scss │ └── style.scss ├── package.json ├── src ├── index.html └── js │ └── bundle.min.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ], 6 | "env": { 7 | "development": { 8 | "presets": [] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://i.imgur.com/DUiL9yn.png) 2 | 3 | # React/Sass/Redux Boilerplate 4 | 5 | Boilerplate and guide for a React/Sass/Redux build. 6 | 7 | ## Getting Started 8 | 9 | To get started, first install all the necessary dependencies. 10 | ``` 11 | > npm install 12 | ``` 13 | 14 | Run an initial webpack build 15 | ``` 16 | > webpack 17 | ``` 18 | 19 | Start the development server (changes will now update live in browser) 20 | ``` 21 | > npm run start 22 | ``` 23 | 24 | To view your project, go to: [http://localhost:3000/](http://localhost:3000/) 25 | 26 | ## Links 27 | 28 | - [Donate](https://www.patreon.com/thenewboston) 29 | - [thenewboston.com](https://thenewboston.com/) 30 | - [Facebook](https://www.facebook.com/TheNewBoston-464114846956315/) 31 | - [Twitter](https://twitter.com/bucky_roberts) 32 | - [Google+](https://plus.google.com/+BuckyRoberts) 33 | - [reddit](https://www.reddit.com/r/thenewboston/) 34 | -------------------------------------------------------------------------------- /dev/js/actions/README.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | Actions are just things that happen *(seriously, that's it)*. 4 | - most actions are user events (clicked a button, submitted a form, etc...) 5 | - can also be other events such as an API call returning data 6 | 7 | ### Actions are (usually) made up of two parts 8 | 9 | 10 | **type** - describes the action that occurred 11 | ``` 12 | ADD_USER_BUTTON_CLICKED 13 | ``` 14 | 15 | 16 | **payload** - *(optional)* any extra data that is needed 17 | ``` 18 | { 19 | first: "Samantha", 20 | last: "Williams", 21 | age: 52, 22 | description: "Samantha is a good woman with a heart of gold." 23 | } 24 | ``` 25 | 26 | ## Actions vs. Action Creators 27 | 28 | Action creators are functions that create objects, actions are the objects that get created. 29 | 30 | **Action creator** 31 | ``` 32 | export default function () { 33 | return { 34 | first: "Samantha", 35 | last: "Williams", 36 | age: 52, 37 | description: "Samantha is a good woman with a heart of gold." 38 | } 39 | } 40 | ``` 41 | 42 | **Action** 43 | ``` 44 | { 45 | first: "Samantha", 46 | last: "Williams", 47 | age: 52, 48 | description: "Samantha is a good woman with a heart of gold." 49 | } 50 | ``` 51 | 52 | ## What happens next? 53 | 54 | All actions are automatically sent to **all** reducers. It is the reducers job to determine how to handle that action 55 | (can also just ignore it). 56 | -------------------------------------------------------------------------------- /dev/js/actions/index.js: -------------------------------------------------------------------------------- 1 | export const selectUser = (user) => { 2 | console.log("You clicked on user: ", user.first); 3 | return { 4 | type: 'USER_SELECTED', 5 | payload: user 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /dev/js/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UserList from '../containers/user-list'; 3 | import UserDetails from '../containers/user-detail'; 4 | require('../../scss/style.scss'); 5 | 6 | const App = () => ( 7 |
8 |

User List

9 | 10 |
11 |

User Details

12 | 13 |
14 | ); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /dev/js/components/README.md: -------------------------------------------------------------------------------- 1 | ## Containers vs. Components 2 | 3 | Containers are very similar to components, the only difference is that containers are aware of application state. If 4 | part of your webpage is only used for displaying data (dumb) then make it a component. If you need it to be smart and 5 | aware of the state (whenever data changes) in the application then make it a container. 6 | -------------------------------------------------------------------------------- /dev/js/containers/README.md: -------------------------------------------------------------------------------- 1 | # Containers 2 | 3 | Containers fetch state data and use it to render (display) components. 4 | - state data will become components props 5 | 6 | Containers are similar to components. However, only containers have access to state data in Redux. 7 | - components are sometimes called "dumb components" or "presentational components" 8 | -------------------------------------------------------------------------------- /dev/js/containers/user-detail.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | /* 5 | * We need "if(!this.props.user)" because we set state to null by default 6 | * */ 7 | 8 | class UserDetail extends Component { 9 | render() { 10 | if (!this.props.user) { 11 | return (
Select a user...
); 12 | } 13 | return ( 14 |
15 | 16 |

{this.props.user.first} {this.props.user.last}

17 |

Age: {this.props.user.age}

18 |

Description: {this.props.user.description}

19 |
20 | ); 21 | } 22 | } 23 | 24 | // "state.activeUser" is set in reducers/index.js 25 | function mapStateToProps(state) { 26 | return { 27 | user: state.activeUser 28 | }; 29 | } 30 | 31 | export default connect(mapStateToProps)(UserDetail); 32 | -------------------------------------------------------------------------------- /dev/js/containers/user-list.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {bindActionCreators} from 'redux'; 3 | import {connect} from 'react-redux'; 4 | import {selectUser} from '../actions/index' 5 | 6 | 7 | class UserList extends Component { 8 | 9 | renderList() { 10 | return this.props.users.map((user) => { 11 | return ( 12 |
  • this.props.selectUser(user)} 15 | > 16 | {user.first} {user.last} 17 |
  • 18 | ); 19 | }); 20 | } 21 | 22 | render() { 23 | return ( 24 | 27 | ); 28 | } 29 | 30 | } 31 | 32 | // Get apps state and pass it as props to UserList 33 | // > whenever state changes, the UserList will automatically re-render 34 | function mapStateToProps(state) { 35 | return { 36 | users: state.users 37 | }; 38 | } 39 | 40 | // Get actions and pass them as props to to UserList 41 | // > now UserList has this.props.selectUser 42 | function matchDispatchToProps(dispatch){ 43 | return bindActionCreators({selectUser: selectUser}, dispatch); 44 | } 45 | 46 | // We don't want to return the plain UserList (component) anymore, we want to return the smart Container 47 | // > UserList is now aware of state and actions 48 | export default connect(mapStateToProps, matchDispatchToProps)(UserList); 49 | -------------------------------------------------------------------------------- /dev/js/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import ReactDOM from "react-dom"; 4 | import {Provider} from 'react-redux'; 5 | import {createStore, applyMiddleware} from 'redux'; 6 | import thunk from 'redux-thunk'; 7 | import promise from 'redux-promise'; 8 | import createLogger from 'redux-logger'; 9 | import allReducers from './reducers'; 10 | import App from './components/App'; 11 | 12 | const logger = createLogger(); 13 | const store = createStore( 14 | allReducers, 15 | applyMiddleware(thunk, promise, logger) 16 | ); 17 | 18 | ReactDOM.render( 19 | 20 | 21 | , 22 | document.getElementById('root') 23 | ); 24 | -------------------------------------------------------------------------------- /dev/js/reducers/README.md: -------------------------------------------------------------------------------- 1 | # Reducers 2 | 3 | Reducers take in actions and update part of application state. 4 | - We combine all reducers into a single object before updated data is dispatched (sent) to store 5 | - Your entire applications state (store) is just whatever gets returned from all your reducers 6 | 7 | ``` 8 | const allReducers = combineReducers({ 9 | users 10 | }); 11 | ``` -------------------------------------------------------------------------------- /dev/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import UserReducer from './reducer-users'; 3 | import ActiveUserReducer from './reducer-active-user'; 4 | 5 | /* 6 | * We combine all reducers into a single object before updated data is dispatched (sent) to store 7 | * Your entire applications state (store) is just whatever gets returned from all your reducers 8 | * */ 9 | 10 | const allReducers = combineReducers({ 11 | users: UserReducer, 12 | activeUser: ActiveUserReducer 13 | }); 14 | 15 | export default allReducers 16 | -------------------------------------------------------------------------------- /dev/js/reducers/reducer-active-user.js: -------------------------------------------------------------------------------- 1 | /* 2 | * All reducers get two parameters passed in, state and action that occurred 3 | * > state isn't entire apps state, only the part of state that this reducer is responsible for 4 | * */ 5 | 6 | // "state = null" is set so that we don't throw an error when app first boots up 7 | export default function (state = null, action) { 8 | switch (action.type) { 9 | case 'USER_SELECTED': 10 | return action.payload; 11 | break; 12 | } 13 | return state; 14 | } 15 | -------------------------------------------------------------------------------- /dev/js/reducers/reducer-users.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The users reducer will always return an array of users no matter what 3 | * You need to return something, so if there are no users then just return an empty array 4 | * */ 5 | 6 | export default function () { 7 | return [ 8 | { 9 | id: 1, 10 | first: "Bucky", 11 | last: "Roberts", 12 | age: 71, 13 | description: "Bucky is a React developer and YouTuber", 14 | thumbnail: "http://i.imgur.com/7yUvePI.jpg" 15 | }, 16 | { 17 | id: 2, 18 | first: "Joby", 19 | last: "Wasilenko", 20 | age: 27, 21 | description: "Joby loves the Packers, cheese, and turtles.", 22 | thumbnail: "http://i.imgur.com/52xRlm8.png" 23 | }, 24 | { 25 | id: 3, 26 | first: "Madison", 27 | last: "Williams", 28 | age: 24, 29 | description: "Madi likes her dog but it is really annoying.", 30 | thumbnail: "http://i.imgur.com/4EMtxHB.png" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /dev/scss/style.scss: -------------------------------------------------------------------------------- 1 | $primary: #006699; 2 | 3 | h2 { 4 | color: $primary; 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-template", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "dev": "webpack", 7 | "start": "webpack-dev-server" 8 | }, 9 | "license": "ISC", 10 | "dependencies": { 11 | "babel-core": "^6.10.4", 12 | "babel-loader": "^6.2.4", 13 | "babel-polyfill": "^6.9.1", 14 | "babel-preset-es2015": "^6.9.0", 15 | "babel-preset-react": "^6.11.1", 16 | "babel-register": "^6.9.0", 17 | "cross-env": "^1.0.8", 18 | "css-loader": "^0.23.1", 19 | "expect": "^1.20.1", 20 | "node-libs-browser": "^1.0.0", 21 | "node-sass": "^3.8.0", 22 | "react": "^15.1.0", 23 | "react-addons-test-utils": "^15.1.0", 24 | "react-dom": "^15.1.0", 25 | "react-redux": "^4.4.5", 26 | "redux": "^3.5.2", 27 | "redux-logger": "^2.6.1", 28 | "redux-promise": "^0.5.3", 29 | "redux-thunk": "^2.1.0", 30 | "sass-loader": "^4.0.0", 31 | "style-loader": "^0.13.1", 32 | "webpack": "^1.13.1", 33 | "webpack-dev-middleware": "^1.6.1", 34 | "webpack-dev-server": "^1.14.1", 35 | "webpack-hot-middleware": "^2.11.0" 36 | }, 37 | "devDependencies": { 38 | "babel-plugin-react-transform": "^2.0.2", 39 | "babel-preset-stage-0": "^6.5.0", 40 | "react-transform-hmr": "^1.0.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Webpack 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devServer: { 6 | inline: true, 7 | contentBase: './src', 8 | port: 3000 9 | }, 10 | devtool: 'cheap-module-eval-source-map', 11 | entry: './dev/js/index.js', 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.js$/, 16 | loaders: ['babel'], 17 | exclude: /node_modules/ 18 | }, 19 | { 20 | test: /\.scss/, 21 | loader: 'style-loader!css-loader!sass-loader' 22 | } 23 | ] 24 | }, 25 | output: { 26 | path: 'src', 27 | filename: 'js/bundle.min.js' 28 | }, 29 | plugins: [ 30 | new webpack.optimize.OccurrenceOrderPlugin() 31 | ] 32 | }; 33 | --------------------------------------------------------------------------------