Skip to content

Commit b7d6ed5

Browse files
committed
Create action and store doc pages
1 parent 19d2b21 commit b7d6ed5

File tree

8 files changed

+327
-311
lines changed

8 files changed

+327
-311
lines changed

docs/src/components/nav.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default React.createClass({
1717
{logo}
1818
<ul id="nav-mobile" className="right hide-on-med-and-down">
1919
<li><a href={urlize("docs/01-getting-started.html")}>Docs</a></li>
20-
<li><a href={urlize("docs/05-api.html")}>API</a></li>
20+
<li><a href={urlize("docs/07-api.html")}>API</a></li>
2121
<li><a href="https://github.com/optimizely/nuclear-js">Github</a></li>
2222
</ul>
2323
</div>

docs/src/docs/01-getting-started.md

Lines changed: 1 addition & 309 deletions
Original file line numberDiff line numberDiff line change
@@ -71,312 +71,4 @@ This is completely optional, but very useful for keeping tracking of dispatched
7171

7272
Now that we have our reactor, let's create some actions.
7373

74-
## Creating Actions
75-
76-
Actions (sometimes called action creators) are functions that you call to send data into the system. In Nuclear, any function that calls
77-
`reactor.dispatch(actionType: string, payload: any)` is categorized as an action.
78-
79-
For our example, we'll start by creating an action to fetch products from a server and another action to add a product to the user's shopping cart.
80-
81-
In order to correctly reference actions throughout the system, we'll create an `actionTypes.js` file, which is simply a collection of constants.
82-
We're using React's keyMirror utility to create the constants, but that's just a convenience — you can create action types in any way you'd like.
83-
They're not even required to be in a separate file, though that's certainly recommended.
84-
85-
#### `actionTypes.js`
86-
87-
```javascript
88-
import keyMirror from 'react/lib/keyMirror'
89-
90-
export default keyMirror({
91-
RECEIVE_PRODUCTS: null,
92-
ADD_TO_CART: null,
93-
CHECKOUT_START: null,
94-
CHECKOUT_SUCCESS: null,
95-
CHECKOUT_FAILED: null,
96-
})
97-
```
98-
99-
#### `actions.js`
100-
101-
```javascript
102-
import shop from '../../common/api/shop'
103-
import reactor from './reactor'
104-
import {
105-
RECEIVE_PRODUCTS,
106-
ADD_TO_CART,
107-
CHECKOUT_START,
108-
CHECKOUT_SUCCESS,
109-
CHECKOUT_FAILED,
110-
} from './actionTypes'
111-
112-
export default {
113-
fetchProducts() {
114-
shop.getProducts(products => {
115-
reactor.dispatch(RECEIVE_PRODUCTS, { products })
116-
});
117-
},
118-
119-
addToCart(product) {
120-
reactor.dispatch(ADD_TO_CART, { product })
121-
},
122-
}
123-
```
124-
125-
We've now created two actions that we can use to send data into the system.
126-
127-
`addToCart` is a simple, synchronous action that takes in a product and dispatches `"ADD_TO_CART"` with the product in the payload.
128-
129-
While synchronous actions are great, often you'll need to perform an asynchronous operation before dispatching an action. Nuclear
130-
fully supports creating actions asynchronously, as we're doing in `fetchProducts`. This is a common pattern you'll use as your application grows,
131-
and Nuclear has no opinion on how you perform your operations: callbacks, Promises, Generators, ES7 async functions — they'll all work just fine!
132-
133-
If you'd like to jump ahead, you can read more about [async actions](./04-async-actions-and-optimistic-updates.html).
134-
135-
Now let's build a few stores.
136-
137-
## Creating Stores
138-
139-
In Flux, stores are used for managing application state, but they don't represent a single record of data like resource models do.
140-
141-
More than simply managing ORM-style objects, **stores manage the state for a particular domain within the application**.
142-
143-
Unlike many other Flux libraries, Nuclear stores hold no state. Instead, they provide a collection of functions that transform current state into new state.
144-
145-
Stores provide a `getInitialState` method, which returns the initial state value that a store will manage, and an `initialize` hook, which is used to define what
146-
actions a store will respond to by attaching handlers.
147-
148-
Each attached handler takes in current state, transforms it according to the action and its payload,
149-
then returns new state. Handlers have the following signature: `handler(currentState: any, payload: any)`. In Nucler, state can only be an ImmutableJS data type,
150-
such as an `Immutable.Map` or an `Immutable.List`, or a JavaScript primitive.
151-
152-
Because stores in Nuclear don't hold state — they simply receive state, transform it, and return new state — there is no need to worry about stores knowing
153-
about other stores. That means no confusing `store.waitsFor` and no cross-pollution of data. In Nuclear, the sole responsibility of a store is to return a portion
154-
of existing or transformed application state. The responsibility of reading application state falls on **Getters**, which we'll cover later.
155-
156-
Let's continue by creating stores for managing products and the user's shopping cart. Create a `stores/ProductStore.js` file and a `stores/CartStore.js` file.
157-
158-
159-
#### `stores/ProductStore.js`
160-
161-
```javascript
162-
import { Store, toImmutable } from 'nuclear-js'
163-
import { RECEIVE_PRODUCTS, ADD_TO_CART } from '../actionTypes'
164-
165-
// example product:
166-
// {"id": 1, "title": "iPad 4 Mini", "price": 500.01, "inventory": 2, "image": "../common/assets/ipad-mini.png"}
167-
168-
export default Store({
169-
getInitialState() {
170-
return toImmutable({})
171-
},
172-
173-
initialize() {
174-
this.on(RECEIVE_PRODUCTS, receiveProducts)
175-
this.on(ADD_TO_CART, decrementInventory)
176-
}
177-
})
178-
179-
// All store handlers transform `(currentState, payload) => (newState)`
180-
181-
/**
182-
* Transforms an array of products to a map keyed by product.id, and merges it
183-
* with the current state.
184-
*/
185-
function receiveProducts(state, { products }) {
186-
let newProducts = toImmutable(products)
187-
.toMap()
188-
.mapKeys((k, v) => v.get('id'))
189-
return state.merge(newProducts)
190-
}
191-
192-
/**
193-
* Decrements the inventory for a product by 1, unless that product has no more
194-
* inventory.
195-
*/
196-
function decrementInventory(state, { product }) {
197-
return state.update(product.id, product => {
198-
let currentInventory = product.get('inventory')
199-
let newInventory = currentInventory > 0 ? currentInventory - 1 : 0;
200-
return product.set('inventory', newInventory)
201-
})
202-
}
203-
```
204-
205-
#### `stores/CartStore.js`
206-
207-
```javascript
208-
import { Store, toImmutable } from 'nuclear-js'
209-
import { ADD_TO_CART } from '../actionTypes'
210-
211-
/**
212-
* CartStores holds the mapping of productId => quantity within itemQty
213-
* and also maintains rollback information for the checkout process
214-
*/
215-
export default Store({
216-
getInitialState() {
217-
return toImmutable({ itemQty: {} })
218-
},
219-
220-
initialize() {
221-
this.on(ADD_TO_CART, addToCart)
222-
}
223-
})
224-
225-
/**
226-
* Increments the quantity for an existing item by 1, or sets the quantity for
227-
* a new item to 1.
228-
*/
229-
function addToCart(state, { product }) {
230-
let id = product.id
231-
return (state.hasIn(['itemQty', id]))
232-
? state.updateIn(['itemQty', id], quantity => quantity + 1)
233-
: state.setIn(['itemQty', id], 1)
234-
}
235-
```
236-
237-
### Registering our stores
238-
239-
Finally, we'll need to register our stores with the reactor we created at the very beginning.
240-
241-
Registering the store with a reactor does two things:
242-
243-
1. Passes every dispatched action to the store
244-
2. Binds the state a store manages to the application state by the key used for registration
245-
246-
Let's register the stores inside of `main.js`.
247-
248-
#### `main.js`
249-
250-
```javascript
251-
import reactor from './reactor'
252-
import ProductStore from './stores/ProductStore'
253-
import CartStore from './stores/CartStore'
254-
255-
reactor.registerStores({
256-
'products': ProductStore,
257-
'cart': CartStore,
258-
})
259-
```
260-
261-
The above stores can be now be accessed with the following keypaths: `['products']` and `['cart']`. We'll cover keypaths in the Getters section.
262-
263-
But first, a recap:
264-
265-
## Recap
266-
267-
At this point we've created actions for fetching products and adding an item to the cart. We also have the `ProductStore` and `CartStore` registered on the reactor.
268-
269-
Let's see what our application state looks like by using the `reactor.evaluate` function:
270-
271-
```javascript
272-
// providing an empty array to `evaluate` will return a snapshot of the entire app state
273-
reactor.evaluate([])
274-
// result
275-
Map {
276-
cart: Map {
277-
itemQty: Map {}
278-
},
279-
products: Map {}
280-
}
281-
282-
// by passing a keypath, `evaluate` will return a more granular piece of app state
283-
reactor.evaluate(['cart'])
284-
// result
285-
Map {
286-
itemQty: Map {}
287-
}
288-
```
289-
290-
The application state is rather empty; each top level key is currently populated by its store's `getInitialState()` method.
291-
292-
Let's see what our application state looks like after we fetch some products.
293-
294-
```javascript
295-
actions.fetchProducts()
296-
```
297-
298-
After the products have been fetched, our app state looks like this:
299-
300-
```javascript
301-
Map {
302-
cart: Map {
303-
itemQty: Map {}
304-
},
305-
products: Map {
306-
1: Map { id: 1, title: "iPad 4 Mini", price: 500.01, inventory: 2, image: "../common/assets/ipad-mini.png" },
307-
2: Map { id: 2, title: "H&M T-Shirt White", price: 10.99, inventory: 10, image: "../common/assets/t-shirt.png" },
308-
3: Map { id: 3, title: "Charli XCX - Sucker CD", price: 19.99, inventory: 5, image: "../common/assets/sucker.png" }
309-
}
310-
}
311-
```
312-
313-
Now let's add a product to our shopping cart using the `addToCart` action we created earlier:
314-
315-
```javascript
316-
actions.addToCart({ id: 3 })
317-
```
318-
319-
Notice that two things occurred:
320-
321-
1. There is an entry in the `itemQty` map
322-
2. The inventory for **Charli XCX - Sucker CD** went from 5 to 4
323-
324-
```javascript
325-
Map {
326-
cart: Map {
327-
itemQty: Map {
328-
3: 1
329-
}
330-
},
331-
products: Map {
332-
1: Map { id: 1, title: "iPad 4 Mini", price: 500.01, inventory: 2, image: "../common/assets/ipad-mini.png" },
333-
2: Map { id: 2, title: "H&M T-Shirt White", price: 10.99, inventory: 10, image: "../common/assets/t-shirt.png" },
334-
3: Map { id: 3, title: "Charli XCX - Sucker CD", price: 19.99, inventory: 4, image: "../common/assets/sucker.png" }
335-
}
336-
}
337-
```
338-
339-
Those two things happened, because our store handlers responded to the `addToCart` action and transformed the app state.
340-
341-
You might think that the information associated with our stores is pretty minimal. For example, the `CartStore` doesn't actually know anything about the product,
342-
such as its title, price or images — all information that we'd need if we were to build a cart component. It only knows that there is a mapping between 3 and 1,
343-
which refers to `<id> => <qty>`.
344-
345-
Minimal data management within our stores is in fact a good practice, because it helps encapsulate and minimize the scope of data management for a particular store.
346-
Remember, each store is supposed to manages only a single particular domain. In the case of the `CartStore`, it only cares about item quantities, so it doesn't need
347-
anything more than an item's id and its quantity count.
348-
349-
However, if stores are limited in scope, how can you read substantive data from the app state?
350-
351-
It's actually quite simple: **composition**.
352-
353-
Nuclear allows you to combine data from stores in a non-destructive manner, check it out:
354-
355-
```javascript
356-
reactor.evaluate([
357-
['cart', 'itemQty'],
358-
['products'],
359-
(itemQty, products) => {
360-
return itemQty.map((qty, itemId) => {
361-
return toImmutable({
362-
product: products.get(itemId),
363-
quantity: qty
364-
})
365-
}).toList()
366-
}
367-
])
368-
369-
// result
370-
List [
371-
Map {
372-
product: Map { id: 3, title: "Charli XCX - Sucker CD", price: 19.99, inventory: 4, image: "../common/assets/sucker.png" },
373-
quantity: 1
374-
}
375-
}
376-
```
377-
378-
If you completely understand the above, that's great! If not, don't worry, this is probably the first **Getter** you've ever seen,
379-
and just in time too! The next section is all about getters, one of the most powerful abstractions in Nuclear.
380-
381-
#### [Next: Getters](./02-getters.html)
382-
74+
#### [Next: Creating Actions](./02-creating-actions.html)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: "Creating Actions"
3+
section: "Guide"
4+
---
5+
6+
# Creating Actions
7+
8+
Actions (sometimes called action creators) are functions that you call to send data into the system. In Nuclear, any function that calls
9+
`reactor.dispatch(actionType: string, payload: any)` is categorized as an action.
10+
11+
For our example, we'll start by creating an action to fetch products from a server and another action to add a product to the user's shopping cart.
12+
13+
In order to correctly reference actions throughout the system, we'll create an `actionTypes.js` file, which is simply a collection of constants.
14+
We're using React's keyMirror utility to create the constants, but that's just a convenience — you can create action types in any way you'd like.
15+
They're not even required to be in a separate file, though that's certainly recommended.
16+
17+
#### `actionTypes.js`
18+
19+
```javascript
20+
import keyMirror from 'react/lib/keyMirror'
21+
22+
export default keyMirror({
23+
RECEIVE_PRODUCTS: null,
24+
ADD_TO_CART: null,
25+
CHECKOUT_START: null,
26+
CHECKOUT_SUCCESS: null,
27+
CHECKOUT_FAILED: null,
28+
})
29+
```
30+
31+
#### `actions.js`
32+
33+
```javascript
34+
import shop from '../../common/api/shop'
35+
import reactor from './reactor'
36+
import {
37+
RECEIVE_PRODUCTS,
38+
ADD_TO_CART,
39+
CHECKOUT_START,
40+
CHECKOUT_SUCCESS,
41+
CHECKOUT_FAILED,
42+
} from './actionTypes'
43+
44+
export default {
45+
fetchProducts() {
46+
shop.getProducts(products => {
47+
reactor.dispatch(RECEIVE_PRODUCTS, { products })
48+
});
49+
},
50+
51+
addToCart(product) {
52+
reactor.dispatch(ADD_TO_CART, { product })
53+
},
54+
}
55+
```
56+
57+
We've now created two actions that we can use to send data into the system.
58+
59+
`addToCart` is a simple, synchronous action that takes in a product and dispatches `"ADD_TO_CART"` with the product in the payload.
60+
61+
While synchronous actions are great, often you'll need to perform an asynchronous operation before dispatching an action. Nuclear
62+
fully supports creating actions asynchronously, as we're doing in `fetchProducts`. This is a common pattern you'll use as your application grows,
63+
and Nuclear has no opinion on how you perform your operations: callbacks, Promises, Generators, ES7 async functions — they'll all work just fine!
64+
65+
If you'd like to jump ahead, you can read more about [async actions](./04-async-actions-and-optimistic-updates.html).
66+
67+
Now let's build a few stores.
68+
69+
#### [Next: Creating Stores](./03-creating-stores.html)

0 commit comments

Comments
 (0)