just my stuff to understand redux
# If you haven't visit the setup-react tutorial, please do.
# Ok, we are going to start a new proyect, you already know
# what's needed with setup-react. I'll modify somethings, to
# make it usable for redux.
# NOTE: I'm not going to explain about state and props, nor components.
# Make a dir to put your app
mkdir $1
cd $1
# Create a package.json
npm init -y
# Install react dependencies
npm i --save react react-dom
# Install babel
npm i --save-dev babel-core babel-loader
# Install babel presets for jsx and es6
npm i --save-dev babel-preset-react babel-preset-es2015
# Configure webpack
# Now our config file changed a little bit to follow some
# conventions. (CONVENTIONS OVER CONFIGU... yah, we
# understood your rails issues, let it go, bro.)
#
# The entry point is now main.js instead of index.js
# The output filename is dist/bundle.js instead of public/bundle.js
# I've seen this a lot. dist stands for distribution.
#
echo """module.exports = {
entry: './src/main.js',
output: {
filename: 'dist/bundle.js',
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015'],
},
},
],
},
};
""" > webpack.config.js
# We have two main dirs:
mkdir src # The one with all our DEVELOPMENT code
mkdir dist # The one with all our PRODUCTION READY code
# Setup our index file inside our distribution dir.
# SENPAI, NOTICE ME: I changed div id="app" to id="root"
# (I have seen both, so you can choose the one you like moar)
echo '''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
''' >> dist/index.html
# And now the cool part... the components
# We are going to create a counter app, with increment
# and decrement buttons, also async actions.
# This is the hello world for react-redux apps.
# The folder structure I'll follow is, kind of, the basic one
# I've seen across the web (coff coff... github repos).
#
# ReactReduxApp
# ├── package.json # NPM stuff
# ├── webpack.config.js # Webpack stuff
# ├── dist # Distribution (production)
# │ ├── index.html # HTML hosting our app
# | └── bundle.js # Contains all our react stuff in a single vanilla js file
# └── src # All our react and dev code
# ├── main.js # The one that renders everything to the DOM
# ├── components # Presentational/Dumb components
# │ └── Counter.js
# ├── containers # Container/Smart components
# │ └── Root.js # the counter container, in this case
# | # The rest are redux stuff that I'll explain in a min
# ├── actions
# │ └── fuelSavingsActions.js
# ├── constants
# │ └── ActionTypes.js
# ├── reducers
# │ ├── fuelSavings.js
# │ └── index.js
# └── store
# └── configureStore.js
#
# For more info in presentational and container components read this:
# https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.ok0r7efyw
# So, we have:
# the first two config files
# and the dist/index
# but we are missing all the src/, stuff.
mkdir src/components
mkdir src/containers
# We'll start with the main.js
# As I said before, the main component will be the one rendering everything
# to the DOM. Remember that react components can only have one child, so this
# component will call our Root component that scopes everything.
echo """import React from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
ReactDOM.render(
<Root />,
document.getElementById('root')
);
""" > src/main.js
# Yeah, that was pretty simple, main.js = render our root component
# Now, let's create the Root component that we are importing in main.js
# this one is a container, so we'll place it in the containers directory.
#
# Also, this component will have the state of the Counter
# so it will be a code-lengthy component... I'll explain it inside
#
# NOTE: I'm not sure of following the best practices. Also, I'm going to continue
# using es6 code, 'cause we are cool. (no, seriously, it has a lot of cool features)
#
# http://stackoverflow.com/questions/22939130/when-should-i-use-arrow-functions-in-ecmascript-6
# https://medium.com/@kaizendad/how-to-write-react-apps-in-es6-1e9b06d29a88#.5cg44ke1z
echo """import React from 'react';
import Counter from '../components/Counter.js';
class Root extends React.Component {
// Ok, now, we have classes... yups!
// So we have constructors that are called when we want to create an instance.
constructor(props) {
// We are calling React.Component constructor, therefore, our class will do
// everything that a React.Component does when is initialized
super(props);
// We'll define the state here.
this.state = {
counter: 0,
};
// functions don't autobind to 'this' in es6...
// what does it mean?
// well... you won't have access to 'this.state', because
// 'this' could mean a lot of things: http://www.sitepoint.com/what-is-this-in-javascript/
//
// so, we'll put this ugly piece of code to tell js that 'this' is 'this'...
//
// I guess we can translate it as:
// > hey js, I have my functions (increment and decrement) and when you
// use them, 'this' meaning is related to the object's instance.
//
// If you want to understand more about this and es6 read this posts:
// http://egorsmirnov.me/2015/05/22/react-and-es6-part1.html
// https://medium.com/@kaizendad/how-to-write-react-apps-in-es6-1e9b06d29a88#.5cg44ke1z
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
}
render() {
return (
<Counter
value={this.state.counter}
increment={this.increment}
decrement={this.decrement}
/>
);
};
increment() {
// Remember to use setState... never modify the state directly!
this.setState({
counter: this.state.counter + 1,
});
};
// DON'T SKIP THIS: we have lolcatz.
// no, really, I lost 30 minutes of my precious life debugging a nonsense thing.
//
// SAY NO TO ++ / -- OPERATORS WHEN USING STATE!
// this.setState({
// counter: this.state.counter++
// });
//
// The code above is not cool. is a living hell that won't work.
// I thought ++ was equal to +1, but no.
// var++ is equivalent to var = var + 1
// and we don't want to modify the state directly.
decrement() {
// setState, NEVER FORGET
this.setState({
counter: this.state.counter - 1,
});
};
};
export default Root;
""" > src/containers/Root.js
# Cool, that wasn't that hard. (that was harder than a contra3 no-deaths run... in hard mode)
# (wait, dude... is too soon to judge. it can be harder, just wait for redux)
# (just keep going. your friends will think you're cool because you know react... NOT)
# (cman...) it was just a container with one value in its state (counter),
# it has some logic (increment and decrement), that's passed
# to the presentational component, in this case Counter.
# Oh, wait, we don't have Counter component yet!
# let's fix that.
echo """import React, { PropTypes } from 'react';
// is cool to have the propTypes first.
// if you don't know what propTypes is, start crying.
// No, wait, is super simple: is an object defining the expected props.
// just that.
// so, we are expecting a value, an increment and a decrement function.
const propTypes = {
value: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
};
// As this component would be presentational/stateless
// we could use some es6 magic explained here:
// https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d#.vvb9nyooe
// I prefer to use stateless components that doesn't specify behavior,
// and just are in charge of how things look.
const Counter = ({value, increment, decrement}) => (
<div>
<h1>Overkill implementation of a counter</h1>
<h3>Counter: {value}</h3>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
Counter.propTypes = propTypes;
export default Counter;
""" > src/components/Counter.js
# We are done with our content app, let's <strike>make it harder</strike>
# manage the state with redux.
# You can build this with:
webpack
# and open it with your intrawebz navigator:
# netscape dist/index.html
#################################################
# IMPLEMENTING REDUX # (state, action) => state #
#################################################
# NOTE: I'm not a UNIX wizard, neither have the time to deep into SED or AWK,
# so, I'll rewrite everything. (lazyness did a critical hit on me)
# To implement redux, we need to understand it first. (not really, but is cool to know)
# Think about redux as a way to organize your app, specifically the state of your app.
# React helped you to separate betweet presentational and container components,
# now, redux will help you with your state.
# redux is a bunch of functional programming patterns to manage your state.
# Is based on Elm (a functiontal programming lang), and its core are three principals:
#
# 1. Single source of truth: instead of a state per component, is a shared single one.
# 2. State is read only: just don't modify it directly
# 3. Changes are made using pure functions: no side effects.
#
# Let's summarize redux:
# State: the minimal representation of the data
# Actions: the minimal representation of the change to the data
# Reducers: functions to change the state depending on actions.
# You can have a lot of reducers on your app, but you can only
# pass one to your store. That's why you have the reducer composition
# pattern. Is so common, that redux give us a help with that using:
# Redux.combineReducers({ stateField1: reducer1, stateField2: reducer2 })
# The keys correspond to the field of the state to manage, and the values
# are the reducers it should call.
# If the keys and values are the same, you can ommit the values as part of
# ES6 magic: http://eslint.org/docs/rules/object-shorthand.html
#
# Store: holds the state object, understand actions, and have reducers.
# redux help us creating the store with a function:
# Redux.createStore(main-reducer)
#
# Any data that gets into your Redux Application gets there by actions.
#
# The components doesn't have 'logic', (almost) none of them.
# they just call action dispatchers that trigger changes to the state.
# they don't know how this changes happen.
# I like to imagine the actions as the API of our state. A set of functions
# to interact with a blackbox.
#
# This blackbox is full of pure functions called reducers, they take
# two arguments: the current state and an action.
# and returns: a new state, by applying the action to the current state,
# if the action is undefined
# it must return de initial state of the application.
# if the action is not understood,
# it returns the current state without any modification.
#
# The blackbox we are talking about is the store, it wraps the state, so
# you can only modify it through actions that trigger reducers, and it gives
# you access to the state with some functions:
# getState(): captain obvious can help us with this one...
# dispatch(action): this is the function that the components will use to
# dispatch actions.
# subscribe(callback): it registers a callback, so you can know when an action
# is being dispatched. It returns an unsubscribe function,
# so you'll find yourself calling something like:
# const unsubscribeCallback = subscribe(callback);
#
# this video is beautiful, please, watch it:
# https://egghead.io/lessons/javascript-redux-implementing-store-from-scratch?series=getting-started-with-redux
#
# Some toughts on pure function:
# there are libraries like immutable that makes this super easy and efficient,
# but if you want to use vanilla es6 methods in your code, this is an small
# cheat sheet:
# NOTE: We have some heavy use of the spread operator ahead.
# Is dangerous to go alone, take this, link (pun intended):
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
# Also, there's some highorder functions that you may find usefull,
# This amazing series explain them:
# https://www.youtube.com/watch?v=BMUiFMZr7vk&list=PL0zVEGEvSaeEd9hlmCXrk5yUyqUag-n84&index=1
#
# - Add to an array:
# `addToArray(array, value) { return [...array, value]; }`
#
# - Remove from an array:
# `removeIndexFromArray(arary, index) { return [...array.slice(0, index), ...array.slice(index + 1)] }`
#
# - Modify a value in an array:
# `TimesTwoInArray(array, index) { return [...array.slice(0, index), array[index] * 2, ...array.slice(index + 1)] }`
#
# - Modify a value in an object inside an array:
# ```
# TimesTwoInObjectInsideArray(array, id) {
# array.map(obj => {
# if (obj.id !== id) { return obj; }
# return Object.assign({}, obj, { value: obj.value * 2 });
# }
# )};
# ```
#
#
# - Add to an array:
# `addToArray(array, value) { return [...array, value]; }`
#
# - Modify from an object:
# `return Object.keys(obj).reduce( (newObj, current) => {
# if (filter(current)) { newObj[current] = obj[current]; }
# return newObj;
# }, {});`
#
# A link on Object.assign
# https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
#
# There's an alternative to Object.assign, using ES7 object spread operator.
# { ...obj, value: obj.value * 2 };
# Is sexy, NSFW. The problem is that we need to use a babel-preset called stage2.
# You know about loaders and presets in webpack (from setup-react), so is up
# to you if you want to include it.
#
# I won't summarize reducers, you can read about them here: http://redux.js.org/docs/basics/Reducers.html
# But I'll highlight that reducer composition pattern allows different people
# on a team to work on the same file without merge-conflicts.
# Is a matter of add, not overwrite.
#
# Every component that needs to call store.dispatch(action) must have access
# to the store. As passing down the store as a props is a pain in the ass,
# we need a way to pass it implicitly. There's a way to do that, by providing
# the root component with a context that have access to the store.
# Here is an amazing explanaiton of how it works:
# https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context?series=getting-started-with-redux
#
# The context API is not stable, however, we can use the same pattern in a
# package call react-redux (is not redux per sé, but some bindings for react)
# with: `import { Provider } from 'react-redux'`.
# To remove all the boilerplate that we need to type to make the context trick
# to work, we use the currying function `connect(mapStateToProps, mapDispatchToProps)(component)`
# from react-redux.
# Everything you need to know to use react-redux is here:
# http://redux.js.org/docs/basics/UsageWithReact.html
#
# Done.
# I guess you are good to go, but if you want to learn more about redux, visit:
# https://github.com/dwyl/learn-redux
#
# Before we start, we are going to install redux
npm i --save redux
#
# Let's start by declaring the actions of our app.
# we do this in constants/ActionTypes.js
mkdir src/constants
echo """export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
""" >> src/constants/ActionTypes.js
# (> WAAAAAT! Are you kidding me??)
# (Remember, this is an overkill implementation...)
# Ok, let me explain this... every action must have a type, that's basically
# a string describing the action.
# By having all of action types on a single file, you can have an idea of what
# the heck an application does.
# Do you ever wished to read source code fluently? (well, keep wishing,
# is not all up to you... it depents a lot of how well is written and organized)
# following this convention, your readers will know every action the app does
# by reading variable declarations.
#
# Now that we know the actions of our app, we will code some functions
# to generate those actions (remember that actions are objects with a type,
# so this functions are more like factories that create those objects for us).
# Why we want to do this boilerplate, really?
# Well, imagine an AddCounter action, that will create another couner to keep track:
#
# let counterId = 0
# {
# type: 'ADD_COUNTER',
# id: counterId++,
# value: 0
# }
#
# If we have the action hardcoded inside the component, de ID would be an issue
# 'cause, what if another component wants to add a counter? how does it knows
# about the ID that must place?
#
mkdir src/actions
echo """import * as types from '../constants/ActionTypes';
// We are requiring the constans we created before
// These are our factories, functions that create actions
export function increment() {
return { type: types.INCREMENT };
}
export function decrement() {
return { type: types.DECREMENT };
}
""" >> src/actions/counterActions.js
# Let's write our reducers that use this actions
mkdir src/reducers
echo """import { INCREMENT, DECREMENT } from '../constants/ActionTypes';
const initialState = {
value: 0,
}
export default function counter(state = initialState, action) {
switch (action.type) {
case INCREMENT:
return Object.assign({}, state, {
value: state.value + 1,
});
case DECREMENT:
return Object.assign({}, state, {
value: state.value - 1,
});
default:
return state;
}
};
""" >> src/reducers/counter.js
# And just to countinue with the nonesense 'overkillness',
# let's create a root reducer that will compose all our reducers:
echo """import { combineReducers } from 'redux';
import counter from './counter';
const rootReducer = combineReducers({
counter,
});
export default rootReducer;
""" >> src/reducers/index.js
# The root reducer would be the one that I'll pass to the createStore,
# it combines every reducer that our app has. So, if you have another one
# just append it to the object here and it would be available.
# If you want to understand reducers read more about JavaScript's Reduce (Array method):
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
#
# and how to reduce an array of Objects:
# http://stackoverflow.com/questions/5732043/javascript-reduce-on-array-of-objects
# Now, the store
mkdir src/store
echo """import { createStore } from 'redux';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(rootReducer, initialState);
};
""" >> src/store/configureStore.js
# We are using a function called configureStore that accepts an initialState,
# this if we want to load the app in a certain state, let's say, you want
# to retrive some store state at localstorage and load it, maybe here can
# be a cool place to do that. (Also, it would be useful when we implement
# react-router, so just accept it as it is, otherwise, you are invited to
# not do this an call createStore(rootReducer) directly instead.
# Well, we have our redux stuff:
# - actions (with optional but a good practice ActionTypes)
# - reducers (with our rootReducer)
# - store (well, a function to create a store with an inital state)
# The only thing that's missing is to integrate it with our components
# We need to have a way to pass the store to our components,
# we will use the Provider technique discussed above. As it is from
# the 'react-redux' package we are going to install it first.
npm i --save react-redux
# Now, let's overwrite our main.js to use it
echo """import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore'
import Root from './containers/Root';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<Root />
</Provider>
, document.getElementById('root')
);
""" > src/main.js
# We have provided our store to the Root component.
# A container component (like Root) is just a React component that uses
# store.subscribe() to read a part of the Redux state tree and supply props
# to a presentational component it renders.
# You could write a container component by hand but is bette to use React-Redux
# to generate container components with connect() function.
#
# We need to define mapStateToProps(), that tells how to transform the current
# Redux store state into the props you want to pass to a presentational component
# you are wrapping (or containing :P).
# In this case, we are passing the state.counter to the Counter component
#
# Also, we need to be able to call store.dispatch(action) to increment and
# decrement the counter. For that, we need to define another function called
# mapDispatchToProps(), it receives the dispatch() method and returns callback
# props that we will pass to the presentational component.
echo """import { connect } from 'react-redux';
import Counter from '../components/Counter.js';
import * as counterActions from '../actions/counterActions.js';
// You can find some examples of how to use this functions here:
// https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
const mapStateToProps = (state) => {
return {
value: state.counter.value
}
}
// Here, we are using some shortcuts, instead of passing mapDispatchToProps,
// we are passing our action creators for counter
const Root = connect(
mapStateToProps,
counterActions
)(Counter);
export default Root;
// in this case, connect() generates something like this:
//
// render() {
// return (
// <Counter
// value={this.state.counter}
// increment={this.increment}
// decrement={this.decrement}
// />
// );
// };
//
""" > src/containers/Root.js
# If we want to add more counters, we create a Counters contianer, and just
# render the Counters in the Root app, instead of Root rendering the counter.
# I opted for the former one because of simplicity, it doesn't show a good
# structure, but hey... it shows something. Please, leave a comment on how
# to improve this, or if I'm completely wrong and you want to remove this
# from the internet before any programmer see it to save the world of
# horrible code.
######################
# IMPLEMENTING DUCKS #
######################
# There's a redux modular implementation called Ducks
# https://github.com/erikras/ducks-modular-redux
# In our counter app, we have separate files for each redux stuff:
# actions and reducers
# We'll have this structure
# ReactReduxDucksApp
# ├── package.json
# ├── webpack.config.js
# ├── dist
# │ ├── index.html
# | └── bundle.js
# └── src
# ├── main.js
# ├── components
# │ └── Counter.js
# ├── containers
# │ └── Root.js
# |
# └── redux
# ├── modules # The ones here are ducks
# │ └── counter.js # Counter duck
# ├── configureStore.js
# └── rootReducer.js
# We'll move everything to the proposed dir structure
mkdir src/redux
mkdir src/redux/modules
# Let's delete the rest of our past dir structure
rm -rf src/actions
rm -rf src/constants
rm -rf src/reducers
rm -rf src/store
# And change a little bit our configureStore and rootReducer
# to match the correct imports sources
echo """import { combineReducers } from 'redux';
import counter from './modules/counter.js';
const rootReducer = combineReducers({
counter,
});
export default rootReducer;
""" > src/redux/rootReducer.js
echo """import { createStore } from 'redux';
import rootReducer from './rootReducer.js';
export default function configureStore(initialState) {
return createStore(rootReducer, initialState);
};
""" > src/redux/configureStore.js
# Now, create our counter duck
echo """// MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE
const INCREMENT = 'my-app/counter/increment';
const DECREMENT = 'my-app/counter/decrement';
// MAY export its action types as UPPER_SNAKE_CASE, if an external reducer
// needs to listen for them, or if it is a published reusable library.
// (We are not doing this here)
// Every duck MUST export default a function called reducer()
// It's supposed that every reducer manage a part of redux state
// we removed the { value: 0 } object, and changed just to the integer
// the counter is managing
export default function reducer(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
// MUST export its action creators as functions
export function increment() {
return { type: INCREMENT };
}
export function decrement() {
return { type: DECREMENT };
}
""" > src/redux/modules/counter.js
# And overwrite our Root.js to import the correct actionCreators
echo """import { connect } from 'react-redux';
import Counter from '../components/Counter.js';
import * as counterActions from '../redux/modules/counter.js';
const mapStateToProps = (state) => {
return {
value: state.counter
}
}
const Root = connect(
mapStateToProps,
counterActions
)(Counter);
export default Root;
""" > src/containers/Root.js
# And our main.js to import correctly the store
echo """import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './redux/configureStore.js';
import Root from './containers/Root';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<Root />
</Provider>
, document.getElementById('root')
);
""" > src/main.js
webpack
echo "We can see our dir structure here:"
echo """.
├── dist
│ ├── bundle.js
│ └── index.html
├── package.json
├── src
│ ├── components
│ │ └── Counter.js
│ ├── containers
│ │ └── Root.js
│ ├── main.js
│ └── redux
│ ├── configureStore.js
│ ├── modules
│ │ └── counter.js
│ └── rootReducer.js
└── webpack.config.js
"""
# Well, that's all for ducks. You can choose whatever you like, just be
# consistent. I don't have the experince to recommend one over the other,
# so, if you have some thoughts or recommendations it'd be cool to share them here
#################
# ASYNC ACTIONS #
#################
# Synchronus actions, are the ones where:
# **EVERY ACTION IS DISPATCHED IMMEDIATELY**.
#
# Async actions are the ones where
# **ACTIONS ARE DISPATCHED AFTER AN EXTERNAL STUFF**,
# this could be a timer, a response from the web server, etc.
#
# Now, in Redux, we have some stuff called actionCreators(), is a function
# that returns action objects:
# actionCreator => {type: ACTION}
#
# So, if we have an async action, it would be something like this one:
# setTimeout(() => {
# store.dispatch(actionCreator)
# }, 5000)
#
# then, if we want to encapsulate it in a function, to not repeat code
# (yeah, is not that much, but imagine that you need to write like 30 lines
# every time you want to dispatch that action... deal with it.)
# it would be something like this:
# asyncActionCreator() {
# myAsyncAction(dispatch) {
# setTimeout(() => {
# dispatch(actionCreator)
# }, 5000);
# }
# }
#
# > Why all the boilerplate? there are two functions... you are scamming here!
#
# Remember that your actions live inside ducks (hehe), you won't have access to
# the dispatch 'cause that's in the presentational component. (assuming you
# are using react-redux.connect())
#
# Sadly, dispatch() only accepts action objects, the problem with this,
# is that we can't do:
# dispatch(asyncActionCreator())
# as we are used to to dispatch actions... 'cause asyncActionCreator
# returns a function instead of a {type: ACTION} (fucking setTimeout!..)
#
# > Why didn't Dan Abramov thought about this before coding redux?
# (how you dare... He is a fucking deity, don't use his name like this.)
#
# > Ok, I'll fucking do it. dispatch(), you'll eat my function, fuck you!
# dispatch(asyncActionCreator())
# > CONVENTIONS OVE...
#
# HEY HEY HEY, what are you doing?!? Reducers only consume actions.
# You can't dispatch other stuff that isn't an action. DUMB ASS!
# Reducers are like veggies... no, they don't shout every 5 minutes:
# "HEY, EVERYBODY, NOTICE ME! I'M VEGGY!"...
# what I'm refering to, is that they just eat one thing: **action objects**.
#
# > You can't stop the train. I won't stop until I can do:
# > dispatch(asyncActionCreator())
#
# *So, at the sixth day, Dan Abramov created thunk-middleware.*
#
# Here, some ascii drawings, because everyone loves ascii.
#
# //////////////// //////////////// /////////////
# // Dispatcher // ---> // Middlework // ---> // Reducer //
# //////////////// //////////////// /////////////
#
# Dispatcher executes the middleware, and the middleware should
# return an action object so reducers can receive its only food.
#
# When using redux's applyMiddleware(thunk-middleware) we let the dispatcher
# to accept other things that aren't action objects. (In case of the
# thunk-middleware, it makes dispatcher to accept functions, with the only
# condition that those functions return an action object... at some point).
#
# > ehm... wat?
#
# A thunk is just a function that helps calling another function.
#
# applyMiddleware is a store enhancer. Now, with all of this, when a dispatcher
# detects a function as an argument, it will pass the dispatch() and getState()
# methods as parameters to the function, something like this:
#
# dispatch(asyncActionCreator())
# ==
# asyncActionCreator()(dispatch)
#
# We don't need the getStore() in our example, just don't complain...
#
# > Hmh... so all the applyMiddleware(thunk-middleware) is just like syntactic
# > sugar to write it the way I was used to?
#
# Yep. Dan Abramov summarizes this mess like :
# Pattern of providing dispatch to a helper function,
# and help Redux “see” such asynchronous action creators as a special
# case of normal action creators rather than totally different functions...
# Which is good because components shouldn’t care whether something happens
# synchronously or asynchronously. We just abstracted that away.
#
# > OHMYGLOB, this redux thingy, such cool!
#
# fucking Dan, you are so cool, dude.
# http://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout/35415559#35415559
# Now, let's rewrite our duck to include an incrementAsync function:
echo """const INCREMENT = 'my-app/counter/increment';
const DECREMENT = 'my-app/counter/decrement';
export default function reducer(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
export function increment() {
return { type: INCREMENT };
}
export function decrement() {
return { type: DECREMENT };
}
// This will trigger increment after 5 seconds...
// If you think about it, async action just triggers other actions
// after some logic.
export function incrementAsync() {
return dispatch => {
setTimeout( () => {
dispatch(increment());
}, 5000);
};
}
""" > src/redux/modules/counter.js
# > That was easy, bro...
# Hey, but I haven't finished, we need to apply redux-thunk middleware,
# so store.dispatch can understand when we are passing a function.
# First, let's add to our project redux-thunk
npm i --save redux-thunk
# Now apply it
echo """import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './rootReducer.js';
export default function configureStore(initialState) {
return createStore(
rootReducer,
applyMiddleware(thunk),
initialState
);
};
""" > src/redux/configureStore.js
# and thanks to connect(), we don't need to modify the container component.
# Just to put a button for incrementAsync() in our presentational component.
echo """import React, { PropTypes } from 'react';
const propTypes = {
value: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
incrementAsync: PropTypes.func.isRequired,
};
const Counter = ({value, increment, decrement, incrementAsync}) => (
<div>
<h1>Overkill implementation of a counter</h1>
<h3>Counter: {value}</h3>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={incrementAsync}>Increment after 5 seconds</button>
</div>
);
Counter.propTypes = propTypes;
export default Counter;
""" > src/components/Counter.js
# That's it!
webpack