├── .babelrc
├── .gitignore
├── 00_introduction.js
├── 01_simple-action-creator.js
├── 02_about-state-and-meet-redux.js
├── 03_simple-reducer.js
├── 04_get-state.js
├── 05_combine-reducers.js
├── 06_dispatch-action.js
├── 07_dispatch-async-action-1.js
├── 08_dispatch-async-action-2.js
├── 09_middleware.js
├── 10_state-subscriber.js
├── 11_Provider-and-connect.js
├── 11_src
├── src
│ ├── action-creators.js
│ ├── application.jsx
│ ├── create-store.js
│ ├── favicon.png
│ ├── home.jsx
│ ├── index.jsx
│ ├── promise-middleware.js
│ ├── reducers.js
│ ├── server.js
│ └── webpack-dev-server.js
└── webpack.config.js
├── 12_final-words.js
├── README.md
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | dist
5 | nested-dispatch.js
6 | test.js
7 | .idea
8 |
--------------------------------------------------------------------------------
/00_introduction.js:
--------------------------------------------------------------------------------
1 | // Tutorial 0 - introduction.js
2 |
3 | // Why this tutorial?
4 | // While trying to learn Redux, I realized that I had accumulated incorrect knowledge about flux through
5 | // articles I read and personal experience. I don't mean that articles about flux are not well written
6 | // but I just didn't grasp concepts correctly. In the end, I was just applying documentation of different
7 | // flux frameworks (Reflux, Flummox, FB Flux) and trying to make them match with the theoretical concept I read
8 | // about (actions / actions creators, store, dispatcher, etc).
9 | // Only when I started using Redux did I realize that flux is more simple than I thought. This is all
10 | // thanks to Redux being very well designed and having removed a lot of "anti-boilerplate features" introduced
11 | // by other frameworks I tried before. I now feel that Redux is a much better way to learn about flux
12 | // than many other frameworks. That's why I want now to share with everyone, using my own words,
13 | // flux concepts that I am starting to grasp, focusing on the use of Redux.
14 |
15 | // You may have seen this diagram representing the famous unidirectional data flow of a flux application:
16 |
17 | /*
18 | _________ ____________ ___________
19 | | | | | | |
20 | | Action |------------▶| Dispatcher |------------▶| callbacks |
21 | |_________| |____________| |___________|
22 | ▲ |
23 | | |
24 | | |
25 | _________ ____|_____ ____▼____
26 | | |◀----| Action | | |
27 | | Web API | | Creators | | Store |
28 | |_________|----▶|__________| |_________|
29 | ▲ |
30 | | |
31 | ____|________ ____________ ____▼____
32 | | User | | React | | Change |
33 | | interactions |◀--------| Views |◀-------------| events |
34 | |______________| |___________| |_________|
35 |
36 | */
37 |
38 | // In this tutorial we'll gradually introduce you to concepts of the diagram above. But instead of trying
39 | // to explain this complete diagram and the overall flow it describes, we'll take each piece separately and try to
40 | // understand why it exists and what role it plays. In the end you'll see that this diagram makes perfect sense
41 | // once we understand each of its parts.
42 |
43 | // But before we start, let's talk a little bit about why flux exists and why we need it...
44 | // Let's pretend we're building a web application. What are all web applications made of?
45 | // 1) Templates / html = View
46 | // 2) Data that will populate our views = Models
47 | // 3) Logic to retrieve data, glue all views together and to react accordingly to user events,
48 | // data modifications, etc. = Controller
49 |
50 | // This is the very classic MVC that we all know about. But it actually looks like concepts of flux,
51 | // just expressed in a slightly different way:
52 | // - Models look like stores
53 | // - user events, data modifications and their handlers look like
54 | // "action creators" -> action -> dispatcher -> callback
55 | // - Views look like React views (or anything else as far as flux is concerned)
56 |
57 | // So is flux just a matter of new vocabulary? Not exactly. But vocabulary DOES matter, because by introducing
58 | // these new terms we are now able to express more precisely things that were regrouped under
59 | // various terminologies... For example, isn't a data fetch an action? Just like a click is also an action?
60 | // And a change in an input is an action too... Then we're all already used to issuing actions from our
61 | // applications, we were just calling them differently. And instead of having handlers for those
62 | // actions directly modify Models or Views, flux ensures all actions go first through something called
63 | // a dispatcher, then through our stores, and finally all watchers of stores are notified.
64 |
65 | // To get more clarity how MVC and flux differ, we'll
66 | // take a classic use-case in an MVC application:
67 | // In a classic MVC application you could easily end up with:
68 | // 1) User clicks on button "A"
69 | // 2) A click handler on button "A" triggers a change on Model "A"
70 | // 3) A change handler on Model "A" triggers a change on Model "B"
71 | // 4) A change handler on Model "B" triggers a change on View "B" that re-renders itself
72 |
73 | // Finding the source of a bug in such an environment when something goes wrong can become quite challenging
74 | // very quickly. This is because every View can watch every Model, and every Model can watch other Models, so
75 | // basically data can arrive from a lot of places and be changed by a lot of sources (any views or any models).
76 |
77 | // Whereas when using flux and its unidirectional data flow, the example above could become:
78 | // 1) user clicks on button "A"
79 | // 2) a handler on button "A" triggers an action that is dispatched and produces a change on Store "A"
80 | // 3) since all other stores are also notified about the action, Store B can react to the same action too
81 | // 4) View "B" gets notified by the change in Stores A and B, and re-renders
82 |
83 | // See how we avoid directly linking Store A to Store B? Each store can only be
84 | // modified by an action and nothing else. And once all stores have replied to an action,
85 | // views can finally update. So in the end, data always flows in one way:
86 | // action -> store -> view -> action -> store -> view -> action -> ...
87 |
88 | // Just as we started our use case above from an action, let's start our tutorial with
89 | // actions and action creators.
90 |
91 | // Go to next tutorial: 01_simple-action-creator.js
92 |
--------------------------------------------------------------------------------
/01_simple-action-creator.js:
--------------------------------------------------------------------------------
1 | // Tutorial 1 - simple-action-creator.js
2 |
3 | // We started to talk a little about actions in the introduction but what exactly are those "action creators"
4 | // and how are they linked to "actions"?
5 |
6 | // It's actually so simple that a few lines of code can explain it all!
7 |
8 | // The action creator is just a function...
9 | var actionCreator = function() {
10 | // ...that creates an action (yeah, the name action creator is pretty obvious now) and returns it
11 | return {
12 | type: 'AN_ACTION'
13 | }
14 | }
15 |
16 | // So is that all? yes.
17 |
18 | // However, one thing to note is the format of the action. This is kind of a convention in flux
19 | // that the action is an object that contains a "type" property. This type allows for further
20 | // handling of the action. Of course, the action can also contain other properties to
21 | // pass any data you want.
22 |
23 | // We'll also see later that the action creator can actually return something other than an action,
24 | // like a function. This will be extremely useful for async action handling (more on that
25 | // in dispatch-async-action.js).
26 |
27 | // We can call this action creator and get an action as expected:
28 | console.log(actionCreator())
29 | // Output: { type: 'AN_ACTION' }
30 |
31 | // Ok, this works but it does not go anywhere...
32 | // What we need is to have this action be sent somewhere so that
33 | // anyone interested could know that something happened and could act accordingly.
34 | // We call this process "Dispatching an action".
35 |
36 | // To dispatch an action we need... a dispatch function ("Captain obvious").
37 | // And to let anyone interested know that an action happened, we need a mechanism to register
38 | // "handlers". Such "handlers" to actions in traditional flux application are called stores and
39 | // we'll see in the next section how they are called in redux.
40 |
41 | // So far here is the flow of our application:
42 | // ActionCreator -> Action
43 |
44 | // Read more about actions and action creators here:
45 | // http://rackt.org/redux/docs/recipes/ReducingBoilerplate.html
46 |
47 | // Go to next tutorial: 02_about-state-and-meet-redux.js
48 |
--------------------------------------------------------------------------------
/02_about-state-and-meet-redux.js:
--------------------------------------------------------------------------------
1 | // Tutorial 02 - about-state-and-meet-redux.js
2 |
3 | // Sometimes the actions that we'll handle in our application will not only inform us
4 | // that something happened but also tell us that data needs to be updated.
5 |
6 | // This is actually quite a big challenge in any app.
7 | // Where do I keep all the data regarding my application along its lifetime?
8 | // How do I handle modification of such data?
9 | // How do I propagate modifications to all parts of my application?
10 |
11 | // Here comes Redux.
12 |
13 | // Redux (https://github.com/rackt/redux) is a "predictable state container for JavaScript apps"
14 |
15 | // Let's review the above questions and reply to them with
16 | // Redux vocabulary (flux vocabulary too for some of them):
17 |
18 | // Where do I keep all the data regarding my application along its lifetime?
19 | // You keep it the way you want (JS object, array, Immutable structure, ...).
20 | // Data of your application will be called state. This makes sense since we're talking about
21 | // all the application's data that will evolve over time, it's really the application's state.
22 | // But you hand it over to Redux (Redux is a "state container", remember?).
23 | // How do I handle data modifications?
24 | // Using reducers (called "stores" in traditional flux).
25 | // A reducer is a subscriber to actions.
26 | // A reducer is just a function that receives the current state of your application, the action,
27 | // and returns a new state modified (or reduced as they call it)
28 | // How do I propagate modifications to all parts of my application?
29 | // Using subscribers to state's modifications.
30 |
31 | // Redux ties all this together for you.
32 | // To sum up, Redux will provide you:
33 | // 1) a place to put your application state
34 | // 2) a mechanism to dispatch actions to modifiers of your application state, AKA reducers
35 | // 3) a mechanism to subscribe to state updates
36 |
37 | // The Redux instance is called a store and can be created like this:
38 | /*
39 | import { createStore } from 'redux'
40 | var store = createStore()
41 | */
42 |
43 | // But if you run the code above, you'll notice that it throws an error:
44 | // Error: Invariant Violation: Expected the reducer to be a function.
45 |
46 | // That's because createStore expects a function that will allow it to reduce your state.
47 |
48 | // Let's try again
49 |
50 | import { createStore } from 'redux'
51 |
52 | var store = createStore(() => {})
53 |
54 | // Seems good for now...
55 |
56 | // Go to next tutorial: 03_simple-reducer.js
57 |
--------------------------------------------------------------------------------
/03_simple-reducer.js:
--------------------------------------------------------------------------------
1 | // Tutorial 03 - simple-reducer.js
2 |
3 | // Now that we know how to create a Redux instance that will hold the state of our application
4 | // we will focus on those reducer functions that will allow us to transform this state.
5 |
6 | // A word about reducer VS store:
7 | // As you may have noticed, in the flux diagram shown in the introduction, we had "Store", not
8 | // "Reducer" like Redux is expecting. So how exactly do Store and Reducer differ?
9 | // It's more simple than you could imagine: A Store keeps your data in it while a Reducer doesn't.
10 | // So in traditional flux, stores hold state in them while in Redux, each time a reducer is
11 | // called, it is passed the state that needs to be updated. This way, Redux's stores became
12 | // "stateless stores" and were renamed reducers.
13 |
14 | // As stated before, when creating a Redux instance you must give it a reducer function...
15 |
16 | import { createStore } from 'redux'
17 |
18 | var store_0 = createStore(() => {})
19 |
20 | // ... so that Redux can call this function on your application state each time an action occurs.
21 | // Giving reducer(s) to createStore is exactly how redux registers the action "handlers" (read reducers) we
22 | // were talking about in section 01_simple-action-creator.js.
23 |
24 | // Let's put some log in our reducer
25 |
26 | var reducer = function (...args) {
27 | console.log('Reducer was called with args', args)
28 | }
29 |
30 | var store_1 = createStore(reducer)
31 |
32 | // Output: Reducer was called with args [ undefined, { type: '@@redux/INIT' } ]
33 |
34 | // Did you see that? Our reducer is actually called even if we didn't dispatch any action...
35 | // That's because to initialize the state of the application,
36 | // Redux actually dispatches an init action ({ type: '@@redux/INIT' })
37 |
38 | // When called, a reducer is given those parameters: (state, action)
39 | // It's then very logical that at an application initialization, the state, not being
40 | // initialized yet, is "undefined"
41 |
42 | // But then what is the state of our application after Redux sends its "init" action?
43 |
44 | // Go to next tutorial: 04_get-state.js
45 |
--------------------------------------------------------------------------------
/04_get-state.js:
--------------------------------------------------------------------------------
1 | // Tutorial 04 - get-state.js
2 |
3 | // How do we retrieve the state from our Redux instance?
4 |
5 | import { createStore } from 'redux'
6 |
7 | var reducer_0 = function (state, action) {
8 | console.log('reducer_0 was called with state', state, 'and action', action)
9 | }
10 |
11 | var store_0 = createStore(reducer_0)
12 | // Output: reducer_0 was called with state undefined and action { type: '@@redux/INIT' }
13 |
14 | // To get the state that Redux is holding for us, you call getState
15 |
16 | console.log('store_0 state after initialization:', store_0.getState())
17 | // Output: store_0 state after initialization: undefined
18 |
19 | // So the state of our application is still undefined after the initialization? Well of course it is,
20 | // our reducer is not doing anything... Remember how we described the expected behavior of a reducer in
21 | // "about-state-and-meet-redux"?
22 | // "A reducer is just a function that receives the current state of your application, the action,
23 | // and returns a new state modified (or reduced as they call it)"
24 | // Our reducer is not returning anything right now so the state of our application is what
25 | // reducer() returns, hence "undefined".
26 |
27 | // Let's try to send an initial state of our application if the state given to reducer is undefined:
28 |
29 | var reducer_1 = function (state, action) {
30 | console.log('reducer_1 was called with state', state, 'and action', action)
31 | if (typeof state === 'undefined') {
32 | return {}
33 | }
34 |
35 | return state;
36 | }
37 |
38 | var store_1 = createStore(reducer_1)
39 | // Output: reducer_1 was called with state undefined and action { type: '@@redux/INIT' }
40 |
41 | console.log('store_1 state after initialization:', store_1.getState())
42 | // Output: store_1 state after initialization: {}
43 |
44 | // As expected, the state returned by Redux after initialization is now {}
45 |
46 | // There is however a much cleaner way to implement this pattern thanks to ES6:
47 |
48 | var reducer_2 = function (state = {}, action) {
49 | console.log('reducer_2 was called with state', state, 'and action', action)
50 |
51 | return state;
52 | }
53 |
54 | var store_2 = createStore(reducer_2)
55 | // Output: reducer_2 was called with state {} and action { type: '@@redux/INIT' }
56 |
57 | console.log('store_2 state after initialization:', store_2.getState())
58 | // Output: store_2 state after initialization: {}
59 |
60 | // You've probably noticed that since we've used the default parameter on state parameter of reducer_2,
61 | // we no longer get undefined as state's value in our reducer's body.
62 |
63 | // Let's now recall that a reducer is only called in response to an action dispatched and
64 | // let's fake a state modification in response to an action type 'SAY_SOMETHING'
65 |
66 | var reducer_3 = function (state = {}, action) {
67 | console.log('reducer_3 was called with state', state, 'and action', action)
68 |
69 | switch (action.type) {
70 | case 'SAY_SOMETHING':
71 | return {
72 | ...state,
73 | message: action.value
74 | }
75 | default:
76 | return state;
77 | }
78 | }
79 |
80 | var store_3 = createStore(reducer_3)
81 | // Output: reducer_3 was called with state {} and action { type: '@@redux/INIT' }
82 |
83 | console.log('store_3 state after initialization:', store_3.getState())
84 | // Output: redux state after initialization: {}
85 |
86 | // Nothing new in our state so far since we did not dispatch any action yet. But there are few
87 | // important things to pay attention to in the last example:
88 | // 0) I assumed that our action contains a type and a value property. The type property is mostly
89 | // a convention in flux actions and the value property could have been anything else.
90 | // 1) You'll often see the pattern involving a switch to respond appropriately
91 | // to an action received in your reducers
92 | // 2) When using a switch, NEVER forget to have a "default: return state" because
93 | // if you don't, you'll end up having your reducer return undefined (and lose your state).
94 | // 3) Notice how we returned a new state made by merging current state with { message: action.value },
95 | // all that thanks to this awesome ES7 notation (Object Spread): { ...state, message: action.value }
96 | // 4) Note also that this ES7 Object Spread notation suits our example because it's doing a shallow
97 | // copy of { message: action.value } over our state (meaning that first level properties of state
98 | // are completely overwritten - as opposed to gracefully merged - by first level property of
99 | // { message: action.value }). But if we had a more complex / nested data structure, you might choose
100 | // to handle your state's updates very differently:
101 | // - using Immutable.js (https://facebook.github.io/immutable-js/)
102 | // - using Object.assign (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
103 | // - using manual merge
104 | // - or whatever other strategy that suits your needs and the structure of your state since
105 | // Redux is absolutely NOT opinionated on this (remember, Redux is a state container).
106 |
107 | // Now that we're starting to handle actions in our reducer let's talk about having multiple reducers and
108 | // combining them.
109 |
110 | // Go to next tutorial: 05_combine-reducers.js
111 |
--------------------------------------------------------------------------------
/05_combine-reducers.js:
--------------------------------------------------------------------------------
1 | // Tutorial 05 - combine-reducers.js
2 |
3 | // We're now starting to get a grasp of what a reducer is...
4 |
5 | var reducer_0 = function (state = {}, action) {
6 | console.log('reducer_0 was called with state', state, 'and action', action)
7 |
8 | switch (action.type) {
9 | case 'SAY_SOMETHING':
10 | return {
11 | ...state,
12 | message: action.value
13 | }
14 | default:
15 | return state;
16 | }
17 | }
18 |
19 | // ... but before going further, we should start wondering what our reducer will look like when
20 | // we'll have tens of actions:
21 |
22 | var reducer_1 = function (state = {}, action) {
23 | console.log('reducer_1 was called with state', state, 'and action', action)
24 |
25 | switch (action.type) {
26 | case 'SAY_SOMETHING':
27 | return {
28 | ...state,
29 | message: action.value
30 | }
31 | case 'DO_SOMETHING':
32 | // ...
33 | case 'LEARN_SOMETHING':
34 | // ...
35 | case 'HEAR_SOMETHING':
36 | // ...
37 | case 'GO_SOMEWHERE':
38 | // ...
39 | // etc.
40 | default:
41 | return state;
42 | }
43 | }
44 |
45 | // It becomes quite evident that a single reducer function cannot hold all our
46 | // application's actions handling (well it could hold it, but it wouldn't be very maintainable...).
47 |
48 | // Luckily for us, Redux doesn't care if we have one reducer or a dozen and it will even help us to
49 | // combine them if we have many!
50 |
51 | // Let's declare 2 reducers
52 |
53 | var userReducer = function (state = {}, action) {
54 | console.log('userReducer was called with state', state, 'and action', action)
55 |
56 | switch (action.type) {
57 | // etc.
58 | default:
59 | return state;
60 | }
61 | }
62 | var itemsReducer = function (state = [], action) {
63 | console.log('itemsReducer was called with state', state, 'and action', action)
64 |
65 | switch (action.type) {
66 | // etc.
67 | default:
68 | return state;
69 | }
70 | }
71 |
72 | // I'd like you to pay special attention to the initial state that was actually given to
73 | // each reducer: userReducer got an initial state in the form of a literal object ({}) while
74 | // itemsReducer got an initial state in the form of an array ([]). This is just to
75 | // make clear that a reducer can actually handle any type of data structure. It's really
76 | // up to you to decide which data structure suits your needs (an object literal, an array,
77 | // a boolean, a string, an immutable structure, ...).
78 |
79 | // With this new multiple reducer approach, we will end up having each reducer handle only
80 | // a slice of our application state.
81 |
82 | // But as we already know, createStore expects just one reducer function.
83 |
84 | // So how do we combine our reducers? And how do we tell Redux that each reducer will only handle
85 | // a slice of our state?
86 | // It's fairly simple. We use Redux combineReducers helper function. combineReducers takes a hash and
87 | // returns a function that, when invoked, will call all our reducers, retrieve the new slice of state and
88 | // reunite them in a state object (a simple hash {}) that Redux is holding.
89 | // Long story short, here is how you create a Redux instance with multiple reducers:
90 |
91 | import { createStore, combineReducers } from 'redux'
92 |
93 | var reducer = combineReducers({
94 | user: userReducer,
95 | items: itemsReducer
96 | })
97 | // Output:
98 | // userReducer was called with state {} and action { type: '@@redux/INIT' }
99 | // userReducer was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_9.r.k.r.i.c.n.m.i' }
100 | // itemsReducer was called with state [] and action { type: '@@redux/INIT' }
101 | // itemsReducer was called with state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_4.f.i.z.l.3.7.s.y.v.i' }
102 | var store_0 = createStore(reducer)
103 | // Output:
104 | // userReducer was called with state {} and action { type: '@@redux/INIT' }
105 | // itemsReducer was called with state [] and action { type: '@@redux/INIT' }
106 |
107 | // As you can see in the output, each reducer is correctly called with the init action @@redux/INIT.
108 | // But what is this other action? This is a sanity check implemented in combineReducers
109 | // to assure that a reducer will always return a state != 'undefined'.
110 | // Please note also that the first invocation of init actions in combineReducers share the same purpose
111 | // as random actions (to do a sanity check).
112 |
113 | console.log('store_0 state after initialization:', store_0.getState())
114 | // Output:
115 | // store_0 state after initialization: { user: {}, items: [] }
116 |
117 | // It's interesting to note that Redux handles our slices of state correctly,
118 | // the final state is indeed a simple hash made of the userReducer's slice and the itemsReducer's slice:
119 | // {
120 | // user: {}, // {} is the slice returned by our userReducer
121 | // items: [] // [] is the slice returned by our itemsReducer
122 | // }
123 |
124 | // Since we initialized the state of each of our reducers with a specific value ({} for userReducer and
125 | // [] for itemsReducer) it's no coincidence that those values are found in the final Redux state.
126 |
127 | // By now we have a good idea of how reducers will work. It would be nice to have some
128 | // actions being dispatched and see the impact on our Redux state.
129 |
130 | // Go to next tutorial: 06_dispatch-action.js
131 |
--------------------------------------------------------------------------------
/06_dispatch-action.js:
--------------------------------------------------------------------------------
1 | // Tutorial 06 - dispatch-action.js
2 |
3 | // So far we've focused on building our reducer(s) and we haven't dispatched any of our own actions.
4 | // We'll keep the same reducers from our previous tutorial and handle a few actions:
5 |
6 | var userReducer = function (state = {}, action) {
7 | console.log('userReducer was called with state', state, 'and action', action)
8 |
9 | switch (action.type) {
10 | case 'SET_NAME':
11 | return {
12 | ...state,
13 | name: action.name
14 | }
15 | default:
16 | return state;
17 | }
18 | }
19 | var itemsReducer = function (state = [], action) {
20 | console.log('itemsReducer was called with state', state, 'and action', action)
21 |
22 | switch (action.type) {
23 | case 'ADD_ITEM':
24 | return [
25 | ...state,
26 | action.item
27 | ]
28 | default:
29 | return state;
30 | }
31 | }
32 |
33 | import { createStore, combineReducers } from 'redux'
34 |
35 | var reducer = combineReducers({
36 | user: userReducer,
37 | items: itemsReducer
38 | })
39 | var store_0 = createStore(reducer)
40 |
41 |
42 | console.log("\n", '### It starts here')
43 | console.log('store_0 state after initialization:', store_0.getState())
44 | // Output:
45 | // store_0 state after initialization: { user: {}, items: [] }
46 |
47 | // Let's dispatch our first action... Remember in 'simple-action-creator.js' we said:
48 | // "To dispatch an action we need... a dispatch function." Captain obvious
49 |
50 | // The dispatch function we're looking for is provided by Redux and will propagate our action
51 | // to all of our reducers! The dispatch function is accessible through the Redux
52 | // instance property "dispatch"
53 |
54 | // To dispatch an action, simply call:
55 |
56 | store_0.dispatch({
57 | type: 'AN_ACTION'
58 | })
59 | // Output:
60 | // userReducer was called with state {} and action { type: 'AN_ACTION' }
61 | // itemsReducer was called with state [] and action { type: 'AN_ACTION' }
62 |
63 | // Each reducer is effectively called but since none of our reducers care about this action type,
64 | // the state is left unchanged:
65 |
66 | console.log('store_0 state after action AN_ACTION:', store_0.getState())
67 | // Output: store_0 state after action AN_ACTION: { user: {}, items: [] }
68 |
69 | // But, wait a minute! Aren't we supposed to use an action creator to send an action? We could indeed
70 | // use an actionCreator but since all it does is return an action it would not bring anything more to
71 | // this example. But for the sake of future difficulties let's do it the right way according to
72 | // flux theory. And let's make this action creator send an action we actually care about:
73 |
74 | var setNameActionCreator = function (name) {
75 | return {
76 | type: 'SET_NAME',
77 | name: name
78 | }
79 | }
80 |
81 | store_0.dispatch(setNameActionCreator('bob'))
82 | // Output:
83 | // userReducer was called with state {} and action { type: 'SET_NAME', name: 'bob' }
84 | // itemsReducer was called with state [] and action { type: 'SET_NAME', name: 'bob' }
85 |
86 | console.log('store_0 state after action SET_NAME:', store_0.getState())
87 | // Output:
88 | // store_0 state after action SET_NAME: { user: { name: 'bob' }, items: [] }
89 |
90 | // We just handled our first action and it changed the state of our application!
91 |
92 | // But this seems too simple and not close enough to a real use-case. For example,
93 | // what if we'd like do some async work in our action creator before dispatching
94 | // the action? We'll talk about that in the next tutorial "dispatch-async-action.js"
95 |
96 | // So far here is the flow of our application
97 | // ActionCreator -> Action -> dispatcher -> reducer
98 |
99 | // Go to next tutorial: 07_dispatch-async-action-1.js
100 |
--------------------------------------------------------------------------------
/07_dispatch-async-action-1.js:
--------------------------------------------------------------------------------
1 | // Tutorial 07 - dispatch-async-action-1.js
2 |
3 | // We previously saw how we can dispatch actions and how those actions will modify
4 | // the state of our application thanks to reducers.
5 |
6 | // But so far we've only considered synchronous actions or, more exactly, action creators
7 | // that produce an action synchronously: when called an action is returned immediately.
8 |
9 | // Let's now imagine a simple asynchronous use-case:
10 | // 1) user clicks on button "Say Hi in 2 seconds"
11 | // 2) When button "A" is clicked, we'd like to show message "Hi" after 2 seconds have elapsed
12 | // 3) 2 seconds later, our view is updated with the message "Hi"
13 |
14 | // Of course this message is part of our application state so we have to save it
15 | // in Redux store. But what we want is to have our store save the message
16 | // only 2 seconds after the action creator is called (because if we were to update our state
17 | // immediately, any subscriber to state's modifications - like our view - would be notified right away
18 | // and would then react to this update 2 seconds too soon).
19 |
20 | // If we were to call an action creator like we did until now...
21 |
22 | import { createStore, combineReducers } from 'redux'
23 |
24 | var reducer = combineReducers({
25 | speaker: function (state = {}, action) {
26 | console.log('speaker was called with state', state, 'and action', action)
27 |
28 | switch (action.type) {
29 | case 'SAY':
30 | return {
31 | ...state,
32 | message: action.message
33 | }
34 | default:
35 | return state;
36 | }
37 | }
38 | })
39 | var store_0 = createStore(reducer)
40 |
41 | var sayActionCreator = function (message) {
42 | return {
43 | type: 'SAY',
44 | message
45 | }
46 | }
47 |
48 | console.log("\n", 'Running our normal action creator:', "\n")
49 |
50 | console.log(new Date());
51 | store_0.dispatch(sayActionCreator('Hi'))
52 |
53 | console.log(new Date());
54 | console.log('store_0 state after action SAY:', store_0.getState())
55 | // Output (skipping initialization output):
56 | // Sun Aug 02 2015 01:03:05 GMT+0200 (CEST)
57 | // speaker was called with state {} and action { type: 'SAY', message: 'Hi' }
58 | // Sun Aug 02 2015 01:03:05 GMT+0200 (CEST)
59 | // store_0 state after action SAY: { speaker: { message: 'Hi' } }
60 |
61 |
62 | // ... then we see that our store is updated immediately.
63 |
64 | // What we'd like instead is an action creator that looks a bit like this:
65 |
66 | var asyncSayActionCreator_0 = function (message) {
67 | setTimeout(function () {
68 | return {
69 | type: 'SAY',
70 | message
71 | }
72 | }, 2000)
73 | }
74 |
75 | // But then our action creator would not return an action, it would return "undefined". So this is not
76 | // quite the solution we're looking for.
77 |
78 | // Here's the trick: instead of returning an action, we'll return a function. And this function will be the
79 | // one to dispatch the action when it seems appropriate to do so. But if we want our function to be able to
80 | // dispatch the action it should be given the dispatch function. Then, this should look like this:
81 |
82 | var asyncSayActionCreator_1 = function (message) {
83 | return function (dispatch) {
84 | setTimeout(function () {
85 | dispatch({
86 | type: 'SAY',
87 | message
88 | })
89 | }, 2000)
90 | }
91 | }
92 |
93 | // Again you'll notice that our action creator is not returning an action, it is returning a function.
94 | // So there is a high chance that our reducers won't know what to do with it. But you never know, so let's
95 | // try it out and find out what happens...
96 |
97 | // Go to next tutorial: 08_dispatch-async-action-2.js
98 |
--------------------------------------------------------------------------------
/08_dispatch-async-action-2.js:
--------------------------------------------------------------------------------
1 | // Tutorial 08 - dispatch-async-action-2.js
2 |
3 | // Let's try to run the first async action creator that we wrote in dispatch-async-action-1.js.
4 |
5 | import { createStore, combineReducers } from 'redux'
6 |
7 | var reducer = combineReducers({
8 | speaker: function (state = {}, action) {
9 | console.log('speaker was called with state', state, 'and action', action)
10 |
11 | switch (action.type) {
12 | case 'SAY':
13 | return {
14 | ...state,
15 | message: action.message
16 | }
17 | default:
18 | return state;
19 | }
20 | }
21 | })
22 | var store_0 = createStore(reducer)
23 |
24 | var asyncSayActionCreator_1 = function (message) {
25 | return function (dispatch) {
26 | setTimeout(function () {
27 | dispatch({
28 | type: 'SAY',
29 | message
30 | })
31 | }, 2000)
32 | }
33 | }
34 |
35 | console.log("\n", 'Running our async action creator:', "\n")
36 | store_0.dispatch(asyncSayActionCreator_1('Hi'))
37 |
38 | // Output:
39 | // ...
40 | // /Users/classtar/Codes/redux-tutorial/node_modules/redux/node_modules/invariant/invariant.js:51
41 | // throw error;
42 | // ^
43 | // Error: Invariant Violation: Actions must be plain objects. Use custom middleware for async actions.
44 | // ...
45 |
46 | // It seems that our function didn't even reach our reducers. But Redux has been kind enough to give us a
47 | // tip: "Use custom middleware for async actions.". It looks like we're on the right path but what is this
48 | // "middleware" thing?
49 |
50 | // Just to reassure you, our action creator asyncSayActionCreator_1 is well-written and will work as expected
51 | // as soon as we've figured out what middleware is and how to use it.
52 |
53 | // Go to next tutorial: 09_middleware.js
54 |
--------------------------------------------------------------------------------
/09_middleware.js:
--------------------------------------------------------------------------------
1 | // Tutorial 09 - middleware.js
2 |
3 | // We left dispatch-async-action-2.js with a new concept: "middleware". Somehow middleware should help us
4 | // to solve async action handling. So what exactly is middleware?
5 |
6 | // Generally speaking middleware is something that goes between parts A and B of an application to
7 | // transform what A sends before passing it to B. So instead of having:
8 | // A -----> B
9 | // we end up having
10 | // A ---> middleware 1 ---> middleware 2 ---> middleware 3 --> ... ---> B
11 |
12 | // How could middleware help us in the Redux context? Well it seems that the function that we are
13 | // returning from our async action creator cannot be handled natively by Redux but if we had a
14 | // middleware between our action creator and our reducers, we could transform this function into something
15 | // that suits Redux:
16 |
17 | // action ---> dispatcher ---> middleware 1 ---> middleware 2 ---> reducers
18 |
19 | // Our middleware will be called each time an action (or whatever else, like a function in our
20 | // async action creator case) is dispatched and it should be able to help our action creator
21 | // dispatch the real action when it wants to (or do nothing - this is a totally valid and
22 | // sometimes desired behavior).
23 |
24 | // In Redux, middleware are functions that must conform to a very specific signature and follow
25 | // a strict structure:
26 | /*
27 | var anyMiddleware = function ({ dispatch, getState }) {
28 | return function(next) {
29 | return function (action) {
30 | // your middleware-specific code goes here
31 | }
32 | }
33 | }
34 | */
35 |
36 | // As you can see above, a middleware is made of 3 nested functions (that will get called sequentially):
37 | // 1) The first level provide the dispatch function and a getState function (if your
38 | // middleware or your action creator needs to read data from state) to the 2 other levels
39 | // 2) The second level provide the next function that will allow you to explicitly hand over
40 | // your transformed input to the next middleware or to Redux (so that Redux can finally call all reducers).
41 | // 3) the third level provides the action received from the previous middleware or from your dispatch
42 | // and can either trigger the next middleware (to let the action continue to flow) or process
43 | // the action in any appropriate way.
44 |
45 | // Those of you who are trained to functional programming may have recognized above an opportunity
46 | // to apply a functional patterns: currying (if you aren't, don't worry, skipping the next 10 lines
47 | // won't affect your redux understanding). Using currying, you could simplify the above function like that:
48 | /*
49 | // "curry" may come any functional programming library (lodash, ramda, etc.)
50 | var thunkMiddleware = curry(
51 | ({dispatch, getState}, next, action) => (
52 | // your middleware-specific code goes here
53 | )
54 | );
55 | */
56 |
57 | // The middleware we have to build for our async action creator is called a thunk middleware and
58 | // its code is provided here: https://github.com/gaearon/redux-thunk.
59 | // Here is what it looks like (with function body translated to es5 for readability):
60 |
61 | var thunkMiddleware = function ({ dispatch, getState }) {
62 | // console.log('Enter thunkMiddleware');
63 | return function(next) {
64 | // console.log('Function "next" provided:', next);
65 | return function (action) {
66 | // console.log('Handling action:', action);
67 | return typeof action === 'function' ?
68 | action(dispatch, getState) :
69 | next(action)
70 | }
71 | }
72 | }
73 |
74 | // To tell Redux that we have one or more middlewares, we must use one of Redux's
75 | // helper functions: applyMiddleware.
76 |
77 | // "applyMiddleware" takes all your middlewares as parameters and returns a function to be called
78 | // with Redux createStore. When this last function is invoked, it will produce "a higher-order
79 | // store that applies middleware to a store's dispatch".
80 | // (from https://github.com/rackt/redux/blob/v1.0.0-rc/src/utils/applyMiddleware.js)
81 |
82 | // Here is how you would integrate a middleware to your Redux store:
83 |
84 | import { createStore, combineReducers, applyMiddleware } from 'redux'
85 |
86 | const finalCreateStore = applyMiddleware(thunkMiddleware)(createStore)
87 | // For multiple middlewares, write: applyMiddleware(middleware1, middleware2, ...)(createStore)
88 |
89 | var reducer = combineReducers({
90 | speaker: function (state = {}, action) {
91 | console.log('speaker was called with state', state, 'and action', action)
92 |
93 | switch (action.type) {
94 | case 'SAY':
95 | return {
96 | ...state,
97 | message: action.message
98 | }
99 | default:
100 | return state
101 | }
102 | }
103 | })
104 |
105 | const store_0 = finalCreateStore(reducer)
106 | // Output:
107 | // speaker was called with state {} and action { type: '@@redux/INIT' }
108 | // speaker was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_s.b.4.z.a.x.a.j.o.r' }
109 | // speaker was called with state {} and action { type: '@@redux/INIT' }
110 |
111 | // Now that we have our middleware-ready store instance, let's try again to dispatch our async action:
112 |
113 | var asyncSayActionCreator_1 = function (message) {
114 | return function (dispatch) {
115 | setTimeout(function () {
116 | console.log(new Date(), 'Dispatch action now:')
117 | dispatch({
118 | type: 'SAY',
119 | message
120 | })
121 | }, 2000)
122 | }
123 | }
124 |
125 | console.log("\n", new Date(), 'Running our async action creator:', "\n")
126 |
127 | store_0.dispatch(asyncSayActionCreator_1('Hi'))
128 | // Output:
129 | // Mon Aug 03 2015 00:01:20 GMT+0200 (CEST) Running our async action creator:
130 | // Mon Aug 03 2015 00:01:22 GMT+0200 (CEST) 'Dispatch action now:'
131 | // speaker was called with state {} and action { type: 'SAY', message: 'Hi' }
132 |
133 | // Our action is correctly dispatched 2 seconds after our call the async action creator!
134 |
135 | // Just for your curiosity, here is how a middleware to log all actions that are dispatched, would
136 | // look like:
137 |
138 | function logMiddleware ({ dispatch, getState }) {
139 | return function(next) {
140 | return function (action) {
141 | console.log('logMiddleware action received:', action)
142 | return next(action)
143 | }
144 | }
145 | }
146 |
147 | // Same below for a middleware to discard all actions that goes through (not very useful as is
148 | // but with a bit of more logic it could selectively discard a few actions while passing others
149 | // to next middleware or Redux):
150 | function discardMiddleware ({ dispatch, getState }) {
151 | return function(next) {
152 | return function (action) {
153 | console.log('discardMiddleware action received:', action)
154 | }
155 | }
156 | }
157 |
158 | // Try to modify finalCreateStore call above by using the logMiddleware and / or the discardMiddleware
159 | // and see what happens...
160 | // For example, using:
161 | // const finalCreateStore = applyMiddleware(discardMiddleware, thunkMiddleware)(createStore)
162 | // should make your actions never reach your thunkMiddleware and even less your reducers.
163 |
164 | // See http://rackt.org/redux/docs/introduction/Ecosystem.html, section Middlewares, to
165 | // see other middleware examples.
166 |
167 | // Let's sum up what we've learned so far:
168 | // 1) We know how to write actions and action creators
169 | // 2) We know how to dispatch our actions
170 | // 3) We know how to handle custom actions like asynchronous actions thanks to middlewares
171 |
172 | // The only missing piece to close the loop of Flux application is to be notified about
173 | // state updates to be able to react to them (by re-rendering our components for example).
174 |
175 | // So how do we subscribe to our Redux store updates?
176 |
177 | // Go to next tutorial: 10_state-subscriber.js
178 |
--------------------------------------------------------------------------------
/10_state-subscriber.js:
--------------------------------------------------------------------------------
1 | // Tutorial 10 - state-subscriber.js
2 |
3 | // We're close to having a complete Flux loop but we still miss one critical part:
4 |
5 | // _________ _________ ___________
6 | // | | | Change | | React |
7 | // | Store |----▶ events |-----▶ Views |
8 | // |_________| |_________| |___________|
9 |
10 | // Without it, we cannot update our views when the store changes.
11 |
12 | // Fortunately, there is a very simple way to "watch" over our Redux's store updates:
13 |
14 | /*
15 | store.subscribe(function() {
16 | // retrieve latest store state here
17 | // Ex:
18 | console.log(store.getState());
19 | })
20 | */
21 |
22 | // Yeah... So simple that it almost makes us believe in Santa Claus again.
23 |
24 | // Let's try this out:
25 |
26 | import { createStore, combineReducers } from 'redux'
27 |
28 | var itemsReducer = function (state = [], action) {
29 | console.log('itemsReducer was called with state', state, 'and action', action)
30 |
31 | switch (action.type) {
32 | case 'ADD_ITEM':
33 | return [
34 | ...state,
35 | action.item
36 | ]
37 | default:
38 | return state;
39 | }
40 | }
41 |
42 | var reducer = combineReducers({ items: itemsReducer })
43 | var store_0 = createStore(reducer)
44 |
45 | store_0.subscribe(function() {
46 | console.log('store_0 has been updated. Latest store state:', store_0.getState());
47 | // Update your views here
48 | })
49 |
50 | var addItemActionCreator = function (item) {
51 | return {
52 | type: 'ADD_ITEM',
53 | item: item
54 | }
55 | }
56 |
57 | store_0.dispatch(addItemActionCreator({ id: 1234, description: 'anything' }))
58 |
59 | // Output:
60 | // ...
61 | // store_0 has been updated. Latest store state: { items: [ { id: 1234, description: 'anything' } ] }
62 |
63 | // Our subscribe callback is correctly called and our store now contains the new item that we added.
64 |
65 | // Theoretically speaking we could stop here. Our Flux loop is closed, we understood all concepts that make
66 | // Flux and we saw that it is not that much of a mystery. But to be honest, there is still a lot to talk
67 | // about and a few things in the last example were intentionally left aside to keep the simplest form of this
68 | // last Flux concept:
69 |
70 | // - Our subscriber callback did not receive the state as a parameter, why?
71 | // - Since we did not receive our new state, we were bound to exploit our closured store (store_0) so this
72 | // solution is not acceptable in a real multi-modules application...
73 | // - How do we actually update our views?
74 | // - How do we unsubscribe from store updates?
75 | // - More generally speaking, how should we integrate Redux with React?
76 |
77 | // We're now entering a more "Redux inside React" specific domain.
78 |
79 | // It is very important to understand that Redux is by no means bound to React. It is really a
80 | // "Predictable state container for JavaScript apps" and you can use it in many ways, a React
81 | // application just being one of them.
82 |
83 | // In that perspective we would be a bit lost if it wasn't for react-redux (https://github.com/rackt/react-redux).
84 | // Previously integrated inside Redux (before 1.0.0), this repository holds all the bindings we need to simplify
85 | // our life when using Redux inside React.
86 |
87 | // Back to our "subscribe" case... Why exactly do we have this subscribe function that seems so simple but at
88 | // the same time also seems to not provide enough features?
89 |
90 | // Its simplicity is actually its power! Redux, with its current minimalist API (including "subscribe") is
91 | // highly extensible and this allows developers to build some crazy products like the Redux DevTools
92 | // (https://github.com/gaearon/redux-devtools).
93 |
94 | // But in the end we still need a "better" API to subscribe to our store changes. That's exactly what react-redux
95 | // brings us: an API that will allow us to seamlessly fill the gap between the raw Redux subscribing mechanism
96 | // and our developer expectations. In the end, you won't need to use "subscribe" directly. Instead you will
97 | // use bindings such as "provide" or "connect" and those will hide from you the "subscribe" method.
98 |
99 | // So yeah, the "subscribe" method will still be used but it will be done through a higher order API that
100 | // handles access to redux state for you.
101 |
102 | // We'll now cover those bindings and show how simple it is to wire your components to Redux's state.
103 |
104 | // Go to next tutorial: 11_Provider-and-connect.js
105 |
--------------------------------------------------------------------------------
/11_Provider-and-connect.js:
--------------------------------------------------------------------------------
1 | // Tutorial 11 - Provider-and-connect.js
2 |
3 | // This is the final tutorial and the one that will show you how to bind together Redux and React.
4 |
5 | // To run this example, you will need a browser.
6 |
7 | // All explanations for this example are inlined in the sources inside ./11_src/src/.
8 |
9 | // Once you've read the lines below, start with 11_src/src/server.js.
10 |
11 | // To build our React application and serve it to a browser, we'll use:
12 | // - A very simple node HTTP server (https://nodejs.org/api/http.html)
13 | // - The awesome Webpack (http://webpack.github.io/) to bundle our application,
14 | // - The magic Webpack Dev Server (http://webpack.github.io/docs/webpack-dev-server.html)
15 | // to serve JS files from a dedicated node server that allows for files watch
16 | // - The incredible React Hot Loader http://gaearon.github.io/react-hot-loader/ (another awesome
17 | // project of Dan Abramov - just in case, he is Redux's author) to have a crazy
18 | // DX (Developer experience) by having our components live-reload in the browser
19 | // while we're tweaking them in our code editor.
20 |
21 | // An important point for those of you who are already using React: this application is built
22 | // upon React 0.14.
23 |
24 | // I won't detail Webpack Dev Server and React Hot Loader setup here since it's done pretty
25 | // well in React Hot Loader's docs.
26 | import webpackDevServer from './11_src/src/webpack-dev-server'
27 | // We request the main server of our app to start it from this file.
28 | import server from './11_src/src/server'
29 |
30 | // Change the port below if port 5050 is already in use for you.
31 | // if port equals X, we'll use X for server's port and X+1 for webpack-dev-server's port
32 | const port = 5050
33 |
34 | // Start our webpack dev server...
35 | webpackDevServer.listen(port)
36 | // ... and our main app server.
37 | server.listen(port)
38 |
39 | console.log(`Server is listening on http://127.0.0.1:${port}`)
40 |
41 | // Go to 11_src/src/server.js...
42 |
43 | // Go to next tutorial: 12_final-words.js
44 |
--------------------------------------------------------------------------------
/11_src/src/action-creators.js:
--------------------------------------------------------------------------------
1 | // Tutorial 12 - Provider-and-connect.js
2 |
3 | // We're using Bluebird (https://github.com/petkaantonov/bluebird) as promise library but you could really
4 | // use any promise lib you want.
5 | import Promise from 'bluebird'
6 |
7 | // Our action creator just gets the current time in a delayed fashion to illustrate the use of the promise
8 | // middleware.
9 |
10 | // The promise middleware works by waiting either:
11 | // 1) an action with this format:
12 | // {
13 | // types: [REQUEST, SUCCESS, FAILURE], // actions types given in this specific order
14 | // promise: function() {
15 | // // return a promise
16 | // }
17 | // }
18 | // 2) or anything else what would be passed to the next middleware or to Redux (actually, with this
19 | // implementation of the promise middleware, the "anything else" has to NOT contain a promise
20 | // property to be passed to the next middleware or Redux)
21 |
22 | // When the promise middleware receives this action, it will create 2 actions from this one:
23 | // 1 action for the REQUEST and later 1 action for the SUCCESS or the FAILURE of the action creator.
24 |
25 | // Again, the code for the promise middleware is not complicated and it is worth having a look
26 | // at it (./promise-middleware.js)
27 |
28 | // The action is delayed by "delay" ms passed as a parameter of the action creator. Try to change
29 | // this value to verify that the delay correctly impacts our UI.
30 | export function getTime(delay) {
31 | return {
32 | types: ['GET_TIME_REQUEST', 'GET_TIME_SUCCESS', 'GET_TIME_FAILURE'],
33 | promise: () => {
34 | return new Promise((resolve, reject) => {
35 | // Just simulating an async request to a server via a setTimeout
36 | setTimeout(() => {
37 | const d = new Date()
38 | const ms = ('000' + d.getMilliseconds()).slice(-3)
39 | resolve({
40 | time: `${d.toString().match(/\d{2}:\d{2}:\d{2}/)[0]}.${ms}`
41 | })
42 | }, delay)
43 | })
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/11_src/src/application.jsx:
--------------------------------------------------------------------------------
1 | // Tutorial 12 - Provider-and-connect.js
2 |
3 | // Now is the time to meet the first binding that redux-react (https://github.com/rackt/react-redux)
4 | // brings to us: the Provider component.
5 |
6 | // Provider is a React Component designed to be used as a wrapper of your application's root component. Its
7 | // purpose is to provide your redux instance to all of your application's components. How it does that does not
8 | // really matter to us but just to let you know, it's using React's context feature (it's undocumented so you
9 | // don't have to know about it, but if you're curious:
10 | // https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html).
11 |
12 | import React from 'react'
13 | import Home from './home'
14 | import { Provider } from 'react-redux'
15 |
16 | export default class Application extends React.Component {
17 | render () {
18 | return (
19 | // As explained above, the Provider must wrap your application's Root component. This way,
20 | // this component and all of its children (even deeply nested ones) will have access to your
21 | // Redux store. Of course, to allow Provider to do that, you must give it the store
22 | // you built previously (via a "store" props).
23 |
118 | redux state = { JSON.stringify(reduxState, null, 2) } 119 |120 |