The first function we will write is the reducer for the counter example.
We will also use the expect
library to make assertions.
function counter(state, action) {
if (typeof state === 'undefined') {
return 0; // If state is undefined, return the initial application state
}
if (action.type === 'INCREMENT') {
return state + 1;
} else if (action.type === 'DECREMENT') {
return state - 1;
} else {
return state; // In case an action is passed in we don't understand
}
}
expect (
counter(0, { type: 'INCREMENT' })
).toEqual(1);
expect (
counter(1, { type: 'INCREMENT' })
).toEqual(2);
expect (
counter(2, { type: 'DECREMENT' })
).toEqual(1);
expect (
counter(1, { type: 'DECREMENT' })
).toEqual(0);
expect (
counter(1, { type: 'SOMETHING_ELSE' })
).toEqual(1);
expect (
counter(undefined, {})
).toEqual(0);
When writing a reducer, if state
is not defined, return an object representing the initial state. In this counter example, we return 0
since our count will start from there. If the action
being passed in isn't one the reducer recognizes, we just return the current state
.
The above code can be rewritten more concisely using ES6 notation and a switch statement:
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// ... `expect` statements as above ...
This section makes use of functions built into Redux. We bring in createStore
using the ES6 destructuring syntax.
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
const { createStore } = Redux; // Redux CDN import syntax
// import { createStore } from 'redux' // npm module syntax
const store = createStore(counter);
The store binds together the 3 principles of Redux:
- Holds the current application state object
- Allows you to dispatch actions
- When you create it, you need to specify the reducer that tells how state is updated with actions.
In this example, we call createStore
with counter
as the reducer that manages the state
updates.
-
getState()
retrieves the current state of the Redux store. If we ranconsole.log(store.getState())
with the code above, we could get0
since it is the initial state of our application. -
dispatch()
is the most commonly used. It is how we dispatch actions to change the state of the application. If we runstore.dispatch( { type: 'INCREMENT' });
followed byconsole.log(store.getState());
we will get1
, which reflects the current state resulting from the INCREMENT action. -
subscribe()
registers a callback that the redux store will call any time an action has been dispatched so you can update the UI of your application to reflect the current application state.
// ... `counter` reducer as above ...
const { createStore } = Redux;
const store = createStore(counter);
store.subscribe(() => {
document.body.innerText = store.getState();
});
document.addEventListener('click', () => {
store.dispatch({ type : 'INCREMENT' })
});
The way the code is above, the initial state (0
) is not rendered to the body, as the rendering occurs in the subscribe callback. This can be remedied by refactoring like so:
const render = () => {
document.body.innerText = store.getState();
};
store.subscribe(render);
render(); // calling once to render the initial state (0), then the subscribe will update subsequently
document.addEventListener('click', () => {
store.dispatch({ type : 'INCREMENT' })
});