Skip to content

Commit 9dd724d

Browse files
committed
Getting Started guide
1 parent 50c496c commit 9dd724d

File tree

2 files changed

+226
-47
lines changed

2 files changed

+226
-47
lines changed

docs/getting-started.md

Lines changed: 225 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,250 @@
1-
### Actions
1+
## Getting Started
2+
3+
Don't be fooled by all the fancy talk about reducers, middleware, higher-order stores — Redux is incredibly simple. If you've ever built a Flux application, you will feel right at home. (If you're new to Flux, it's easy, too!)
4+
5+
In this guide, we'll walk through the process of creating a simple Todo app.
6+
7+
## Actions
8+
9+
First, let's define some actions.
10+
11+
**Actions** are payloads of information that send data from your application to your store. They are the *only* source of information for a store. You send them to the store using `store.dispatch()`.
12+
13+
Here's an example action which represents adding a new todo item:
214

315
```js
4-
// Still using constants...
5-
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
16+
{
17+
type: ADD_TODO,
18+
payload: {
19+
text: 'Build my first Redux app'
20+
}
21+
}
22+
```
623

7-
// But action creators are pure functions returning actions
8-
export function increment() {
9-
return {
10-
type: INCREMENT_COUNTER
11-
};
24+
Actions are plain JavaScript objects. By convention, actions should a have a `type` field that indicates the type of action being performed. Types should typically be defined as constants and imported from another module.
25+
26+
```js
27+
import { ADD_TODO, REMOVE_TODO } from '../actionTypes';
28+
```
29+
30+
Other than `type`, the structure of an action object is really up to you. If you're interested, check out [Flux Standard Action](https://github.com/acdlite/flux-standard-action) for recommendations on how actions should be constructed.
31+
32+
We need one more action type, for removing todos:
33+
34+
```js
35+
{
36+
type: REMOVE_TODO,
37+
payload: 123
1238
}
39+
```
1340

14-
export function decrement() {
15-
return {
16-
type: DECREMENT_COUNTER
41+
`payload` in this case indicates the id of the todo we want to remove.
42+
43+
## Action creators
44+
45+
**Action creators** exactly that — functions that create actions. It's easy to conflate the terms "action" and "action creator," so do your best to use the proper term.
46+
47+
In *other* Flux implementations, action creators often trigger a dispatch when invoked, like so:
48+
49+
```js
50+
function addTodoWithDispatch(text) {
51+
const action = {
52+
type: ADD_TODO,
53+
payload: {
54+
text
55+
}
1756
};
57+
dispatch(action);
1858
}
59+
```
60+
61+
By contrast, in Redux action creators are **pure functions** with zero side-effects. They simply return an action:
1962

20-
// Can also be async if you return a function
21-
export function incrementAsync() {
22-
return dispatch => {
23-
setTimeout(() => {
24-
// Yay! Can invoke sync or async actions with `dispatch`
25-
dispatch(increment());
26-
}, 1000);
63+
```js
64+
function addTodo(text) {
65+
return {
66+
type: ADD_TODO,
67+
payload: {
68+
text
69+
}
2770
};
2871
}
72+
```
2973

74+
This makes them more portable and easier to test. To actually initiate a dispatch, pass the result to the `dispatch()` function:
3075

31-
// Could also read state of a store in the callback form
32-
export function incrementIfOdd() {
33-
return (dispatch, getState) => {
34-
const { counter } = getState();
76+
```js
77+
dispatch(addTodo(text));
78+
dispatch(removeTodo(id));
79+
```
3580

36-
if (counter % 2 === 0) {
37-
return;
38-
}
81+
Or create **bound action creator** that automatically dispatches:
3982

40-
dispatch(increment());
41-
};
42-
}
83+
```js
84+
const boundAddTodo = text => dispatch(addTodo(text));
85+
const boundRemoveTodo = id => dispatch(addTodo(id));
4386
```
4487

45-
### Stores
88+
This is an example of a **higher-order function**. You'll notice this pattern a lot when working with Redux. Don't be scared, though — a higher-order function is just a function that returns another function. As you can see, ES6 arrow functions make working with higher-order functions a breeze.
89+
90+
The `dispatch()` function can be accessed directly from the store as `store.dispatch()`, but more likely you'll access it using a helper like react-redux's `connect()`.
91+
92+
## Reducers
93+
94+
Now let's set up our store to respond to the action we defined above.
95+
96+
Unlike other Flux implementations, Redux only has a single store, rather than a different store for each domain of data in your application. Instead of creating multiple stores that manually manage their own internal state, we create **reducers** that specify how to calculate state in response to new actions.
97+
98+
A reducer looks like this:
99+
46100
```js
47-
// ... too, use constants
48-
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../constants/ActionTypes';
101+
(previousState, action) => newState
102+
```
103+
104+
It's the type of function you would pass to `Array.prototype.reduce(reducer, ?initialValue)`.
49105

50-
// what's important is that Store is a pure function,
51-
// and you can write it anyhow you like.
106+
This may seem radical, but it turns out that this function signature is sufficient to express the entirety of the store model from traditional Flux — in a purely functional way. **This is a essence of Redux**. It's what enables all the cool features like hot reloading, time travel, and universal rendering. Aside from all that, though, it's simply a better model for expressing state changes.
52107

53-
// the Store signature is (state, action) => state,
54-
// and the state shape is up to you: you can use primitives,
55-
// objects, arrays, or even ImmutableJS objects.
108+
[**See Dan's talk at React Europe for more on this topic**](https://www.youtube.com/watch?v=xsSnOQynTHs).
109+
110+
Let's make a reducer for our Todo app:
111+
112+
```js
113+
const initialState = { todos: [], idCounter: 0 };
56114

57-
export default function counter(state = 0, action) {
58-
// this function returns the new state when an action comes
115+
function todoReducer(state = initialState, action) {
59116
switch (action.type) {
60-
case INCREMENT_COUNTER:
61-
return state + 1;
62-
case DECREMENT_COUNTER:
63-
return state - 1;
64-
default:
65-
return state;
117+
case ADD_TODO:
118+
return {
119+
...state,
120+
todos: [
121+
...state.todos,
122+
{ text: action.payload, id: state.idCounter + 1 }
123+
],
124+
idCounter: state.idCounter + 1
125+
};
126+
case REMOVE_TODO:
127+
return {
128+
...state,
129+
todos: state.todos.filter(todo => todo.id !== action.payload)
130+
};
131+
default:
132+
return state;
66133
}
134+
}
135+
```
136+
137+
Whoa, what's going on here? A few things to note:
138+
139+
- `state` is the previous state of the store. Redux will dispatch a dummy action immediately upon creating your store, at which point `state` is undefined. From that point on, `state` is the previous value returned by the reducer.
140+
- We're using a default parameter to specify the initial state of the store.
141+
- We're using a switch statement to check the action type.
142+
- **We're not mutating the previous state** — we're returning a **new** state object based on the **previous** state object.
143+
144+
That last point is especially important: never mutate the previous state object. Always return a new state. Remember, reducers are pure functions, and should not perform mutations or side-effects. Here we're using the ES7 spread operator to shallow copy old state values to a new object. You can use a library like Immutable.js for a nicer API and better performance, since it uses [persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure). Here's how that same store would look using immutable values:
145+
146+
```js
147+
const initialState = Immutable.Map({ todos: [], idCounter: 0 });
67148

68-
// BUT THAT'S A SWITCH STATEMENT!
69-
// Right. If you hate 'em, see the FAQ below.
149+
function todoReducer(state = initialState, action) {
150+
switch (action.type) {
151+
case ADD_TODO:
152+
return state
153+
.update('todos',
154+
todos => todos.push(Immutable.Map({
155+
text: action.payload,
156+
id: state.get('idCounter')
157+
})
158+
))
159+
.set('idCounter', state.get('idCounter') + 1);
160+
case REMOVE_TODO:
161+
return state
162+
.update('todos',
163+
todos => todos.filter(todo => todo.id !== action.payload )
164+
);
165+
default:
166+
return state;
167+
}
70168
}
71169
```
170+
171+
If you're thinking "yuck, switch statements," remember that reducers are just functions — you can abstract away these details using helpers. Check out [redux-actions](https://github.com/acdlite/redux-actions) for an example of how to use higher-order functions to create reducers.
172+
173+
## Creating a store
174+
175+
Now we have some action creators and a reducer to handle them. The next step is to create our store.
176+
177+
To create a store, pass a reducer to `createStore()`:
178+
179+
```js
180+
import { createStore } from 'redux';
181+
import todoReducer from '../reducers/todos';
182+
const store = createStore(todoReducer);
183+
```
184+
185+
Usually you'll have multiple reducers for different domains of data in your app. You can use the `combineReducers()` helper to combine multiple reducers into one:
186+
187+
```js
188+
import { createStore, combineReducers } from 'redux';
189+
import * as reducers from '../reducers';
190+
const reducer = combine(reducers);
191+
const store = createStore(reducer);
192+
```
193+
194+
For example, if the object passed to `combineReducers()` looks like this:
195+
196+
```js
197+
const reducers = {
198+
todos: todoReducer,
199+
counter: counterReducer
200+
};
201+
```
202+
203+
It will create a reducer which produces a state object like this:
204+
205+
```js
206+
const state = {
207+
todos: todoState,
208+
counter: counterState
209+
};
210+
```
211+
212+
## Middleware
213+
214+
Middleware is an optional feature of Redux that enables you to customize how dispatches are handled. Think of middleware as a certain type of plugin or extension for Redux.
215+
216+
A common use for middleware is to enable asynchronous dispatches. For example, a promise middleware adds support for dispatching promises:
217+
218+
```js
219+
dispatch(Promise.resolve({ type: ADD_TODO, payload: { text: 'Use middleware!' } }));
220+
```
221+
A promise middleware would detect that a promise was dispatched, intercept it, and instead dispatch with the resolved value at a future point in time.
222+
223+
Middleware is very simple to create using function composition. We won't focus on how middleware works in this document but here's how you enable it when creating your store:
224+
225+
```js
226+
import { createStore, applyMiddleware } from 'redux';
227+
// where promise, thunk, and observable are examples of middleware
228+
const store = applyMiddleware(promise, thunk, observable)(createStore)(reducer);
229+
```
230+
231+
Yes, you read that correctly. [Read more about how middleware works here.](middleware.md)
232+
233+
## Connecting to your views
234+
235+
You'll rarely interact with the store object directly. Most often, you'll use some sort of binding to your preferred view library.
236+
237+
Flux is most popular within the React community, but Redux works with any kind of view layer. The React bindings for Redux are available as react-redux — see that project for details on how to integrate with React.
238+
239+
However, if you do find yourself needing to access the store directly, the API for doing so is very simple:
240+
241+
- `store.dispatch()` dispatches an action.
242+
- `store.getState()` gets the current state.
243+
- `store.subscribe()` registers a listener which is called after every dispatch, and returns a function which you call to unsubscribe.
244+
245+
246+
## Go make something great
247+
248+
That's it! As you can see, despite the powerful features that Redux enables, the core of Redux is really quite simple.
249+
250+
Please let us know if you have suggestions for how this guide could be improved.

docs/types-and-terminology.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Middleware is composable using function composition.
7676
### Store
7777

7878
```js
79-
type Store = { dispatch: Dispatch, getState: State, subscribe: Function, getReducer: Reducer, replaceReducer: void }
79+
type Store = { dispatch: Dispatch, getState: () => State, subscribe: () => Function, getReducer: () => Reducer, replaceReducer: (reducer: Reducer) => void }
8080
```
8181

8282
A store is an object of bound methods to an underlying class instance.

0 commit comments

Comments
 (0)