├── .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 | 
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 |
25 | {this.renderList()}
26 |
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 |
--------------------------------------------------------------------------------