## Table of contents - Store - [Creation](#store-creation) - [Injection](#store-injection) - [Redux chrome devtools](#redux-chrome-devtools) - [Redux-form](#redux-form) - [Reducer](#reducer) - [Decorator](#decorator) - [Field](#field) - [Adapter HoC](#adapterhoc) - [Validation](#validation) - [Data persistance](#data-persistance) - [React and redux](#redux-and-react) - [Ducks and utilities](#ducks-and-utilitites) - [Official bindings](#react-redux) Redux is a predictable state container and will be the library used to store the state of the checkout form, as well as any other relevant state that comes up. This guide assumes you already have a basic knowlegde of what [actions](http://redux.js.org/docs/basics/Actions.html), [reducers](http://redux.js.org/docs/basics/Reducers.html) and a [store](http://redux.js.org/docs/basics/Store.html) is. The first thing we need to do is to download the libraries `redux` and `react-redux`. The latter contains the necessary bindings for React, so that you don't have to wire things up manually. Keep in mind redux is not uniquely linked to React, redux is agnostic about the framework used for rendering the UI and could also be used with Angular for instance. ``` yarn add redux react-redux ``` ## Store creation Second step is to create a file where we configure our redux store. Normallly that's done by creating a folder called _store_ and placing inside a single file called `configureStore.js` (or `store.js` or `index.js`). The relative location in the project should be `src/js/store/configureStore.js`. Below you can find the scaffolding of a basic redux store: ```javascript import { createStore } from 'redux'; const configureStore = () => { const reducer = (state = {}, action) => state; return createStore(reducer); }; export default configureStore; ``` `createStore` function takes 3 arguments, but only the first one is required, which is the root reducer. Remember that a reducer is a function that takes the previous state and an action and calculates the new state of the app. For starters and being able to set up the store, let's just add that dummy reducer function that just returns always `{}` I like to create a function that wraps the store creation to avoid confussions. ES6 modules are singletons. That is, there's only one instance of the module, which maintains its state. Every time you import that module into another module, you get a reference to the one centralized instance. That means we could have just avoided that function encapsulation and export directly the store as: ```javascript const reducer = (state = {}, action) => state; const store = createStore(reducer); export default store; ``` However, with the encapsulation it may be clearer when we perform the store instantiation (from the root React component), since we'll be calling something like `configureStore()`, stating that's a one-time operation and shouldn't be done from anywhere else. ## Store injection Once we have defined the store creation, we need to make our React app aware of it. Let's refactor a bit our entry point by: - Creating an `index.js` file under `src/js/` to conduct React bootstrap into the DOM and setup Hot Module Reloading. ```javascript import React from 'react'; import { render } from 'react-dom'; import App from './components/App'; render(, document.getElementById('root')); if (module.hot) { module.hot.accept(); // HMR for JS // HMR For CSS modules document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => { const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`); link.href = nextStyleHref; // eslint-disable-line no-param-reassign }); } ``` - Moving `App.jsx` inside `src/js/components` and using `Provider` component from `react-redux` to inject the store down the tree. ```javascript import React from 'react'; import { Provider } from 'react-redux'; import configureStore from '../store'; import Header from './Header'; const store = configureStore(); const App = () => (
); export default App; ``` What `Provider` does is making the store instance available to all children components by using context. Don't give too much credit to context, it's still an experimental React feature, but comes in handy when you want to pass data through the component tree without having to pass the props down manually at every level, hence a perfect suit for our store. ## Redux chrome devTools Last, but not least, let's leverage the amazing redux chrome extensions: - Download it from the [Chrome store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) - Go to `configureStore.js` file and make this small tweak. ```javascript return createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); ``` Congratulations! The store setup process is completed and you are realising how easy is to configure it (or maybe it's the amazing writer that is really doing a good job πŸš€ ) ## Redux-form [Redux-form](http://redux-form.com/6.4.3/) is a library that aims to ease management of a form state in Redux, so that we don't have to manage each form input state ourselves, nor the global state form (submission, isTouched, validation...). redux-form primarily consists of four things: - A Redux reducer that listens to dispatched redux-form actions to maintain your form state in Redux. - A React component decorator that wraps your entire form in a Higher Order Component (HOC) and provides functionality via props. - A Field component to connect your individual field inputs to the Redux store. - Various Action Creators for interacting with your forms throughout the application. ### Reducer We need to give the redux-form reducer to Redux. Going to our store creation file: ```javascript import { createStore, combineReducers } from 'redux'; import { reducer as formReducer } from 'redux-form'; const configureStore = () => { const reducers = combineReducers({ form: formReducer, }); return createStore( reducers, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); }; export default configureStore; ``` Keep in mind there is only one global reducer in our app. `combineReducers` is an utility helper that allows us to split the reducing function into separate functions, each managing independent parts of the state. That means our global state object will have now a key called `form`, where all the form state will live. ### Decorator Decorate your form component with reduxForm(). This will provide your component with props that provide information about form state and functions to submit your form. Creating a `Form` component for that purpose seems like a good solution. As an example: ```javascript import React, { PropTypes } from 'react'; import { Field, reduxForm } from 'redux-form'; import InputBox from './InputBox'; const Form = () => (
); // Decorate the form component export default reduxForm({ form: 'checkout', // a unique name for this form })(Form); ``` `reduxForm` is a [Higher Order Component](https://facebook.github.io/react/docs/higher-order-components.html) (aka HOC) o decorator. Simply put, it's a function that takes a React Component and returns a new one. One of the typical applications is to return an "enhanced" version of the component passed in, with those enhancements passed down in form of props. ### Field As you can see above, we have wrapped `InputBox` into the `Field` component provided by `react-redux` ```javascript ``` The Field component is how you connect each individual input to the Redux store. There are three fundamental things that you need to understand in order to use Field correctly: - The name prop is required. It is a string path, in dot-and-bracket notation, corresponding to a value in the form values. It may be as simple as 'firstName' or as complicated as contact.billing.address[2].phones[1].areaCode. - The component prop is required. It may be a Component, a stateless function, or string name of one of the default supported DOM inputs (input, textarea or select). In our case we are passing `InputBox` Component - All other props will be passed along to the element generated by the component prop. `withBorder` will be passed down to our original `InputBox`. ### AdapterHOC In order to not alter the API defined for all our individual components that define the checkout form and decouple them from redux-form library, we can create a HOC that maps props, acting as an adapter. That way, if one day we decide to move away from redux-form, our components remain reusable and composable without the pain of having to refactor them again. We could create something simple like: ```javascript import React from 'react'; function fieldMapper(ReactComponent) { return function TransformedField(props) { return ; // eslint-disable-line react/prop-types }; } export default fieldMapper; ``` The only downside is we'd have to stick to the [naming of input props used by react-redux](http://redux-form.com/6.4.3/docs/api/Field.md/#props), since the only thing the adapter is doing so far is destructuring everything under `input` prop. It's not bad at all since the naming they follow is quite standard (i.e. *checked* instead of *isChecked*), so that would involve still some small tweaks in our component props. To make the HOC more flexible, we could provide a config option as 2nd argument to fieldMapper that would indicate how we want to perform the mapping, some object like `{ checked: 'isChecked' }` To use it in our custom components, we have to export the adapted version: ```javascript import fieldMapper from '../fieldMapper'; ... export default fieldMapper(InputBox); ``` ### Validation There are two ways to provide synchronous client-side validation to the checkout form. The first is to provide redux-form with a validation function that takes an object of form values and returns an object of errors. This is done by providing the validation function to the decorator as a config parameter, or to the decorated form component as a prop. The second is to use individual validators for each field. I'd recommend the 2nd approach since it provides us more modularity, so I'll focus on that one. Each `Field` component accepts a `validate` prop which can be a function or an array of function validators. We can define our validators in `js/utils/validators.js`: ```javascript // Examples of function validators export const required = value => value ? undefined : 'Required' export const maxLength = max => value => value && value.length > max ? `Must be ${max} characters or less` : undefined export const maxLength15 = maxLength(15) export const number = value => value && isNaN(Number(value)) ? 'Must be a number' : undefined export const minValue = min => value => value && value < min ? `Must be at least ${min}` : undefined export const email = value => value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 'Invalid email address' : undefined ``` And then import them, so that the `Field` components we define can perform the needed validation. Following the example with the `InputBox` component: ```javascript import React, { PropTypes } from 'react'; import { Field, reduxForm } from 'redux-form'; import InputBox from './InputBox'; import * as validation from '../utils/validation'; const Form = () => (
); // Decorate the form component export default reduxForm({ form: 'checkout', // a unique name for this form })(Form); ``` Whenever we have an validation error, we'll receive it in our `InputBox` (or any other component we have defined and wrapped with a `Field`, such as `InputText`) as an `error` prop, thanks to our adapter. That would imply that if we don't tick that InputBox and we try to submit the form, validation will yell at us, preventing the form to be submitted and in the end having `error='Required'` inside `InputBox`, as we have defined the message to be in our validator helper. How to display that error depends of the design, but it could be something along this lines: ```javascript // Render of InputBox
{error && {error}} // <-- Display error if we have one
``` ## Data persistance In order to preserve the form state across page reloads, there is a library that plays nicely with redux and can really help us with that task, called [redux-persist](https://github.com/rt2zz/redux-persist). Its usage is pretty straightforward, we just need to modify the store creation part. ```javascript import { createStore, combineReducers, compose } from 'redux'; import { reducer as formReducer } from 'redux-form'; import { persistStore, autoRehydrate } from 'redux-persist'; const reducers = combineReducers({ form: formReducer, }); const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // eslint-disable-line no-underscore-dangle const store = createStore( reducers, composeEnhancers( autoRehydrate() ) ); persistStore(store, { debounce: 500, whitelist: ['form'], }); export default store; ``` And that's really it! Now our form state is kept across page reloads πŸ˜ƒ You don't need to exhaustevely understand the code, but the idea is that `composeEnhancers` or `compose` allows us to enhance the capabilites of the basic `createStore`, such as with the addition of middleware or data persistance (like in this case). `autorehydrate` checks if we have previous state stashed into localStorage, parses it and feeds our store with it on startup. `persistStore` defines _how_ and _what_ we want to persist, in our case, writing into storage every 500ms and only serializing and saving our form state (everything under `state.form`) ## Redux and React So far we have seen how `redux-form` works and how it interacts with `redux`. Now it's turn to shed some light on how to create any other state needed for our app, as well as how to imperatively dispatch actions ourselves, in order to be able to change the state of our app. But first let me step back to remind you the 3 core principles of redux: - **Single source of truth**: the state of your whole application is stored in an object tree within a single store - State is read-only: **the only way to change the state is to emit (aka dispatch) an action, an object describing what happened** - Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers. **Remember to always return new state objects, instead of mutating the previous state** Being said that and making sure myself you tatoo those 3 principles in your skin, let's move forward. The 3 pieces we need to put together are _Action types_, _Action Creators_ and _Reducers_. The traditional approach follows splitting those 3 pieces into 3 separate files. However, as you are introducing new functionality in your redux app, you'll find yourself needing to add `{actionTypes, actions, reducer}` tuples again and again. Most of the time there will be a unique correlation between action and reducer, so it makes more sense for these pieces to be grouped together in an isolated module that is self contained, instead of having to poke around in three different places per new functionality ### Ducks and utilitites Erik Rasmussen came with a proposal called [_ducks_](https://github.com/erikras/ducks-modular-redux), to define each one of these self contained modules. There are a few rules which are handy to keep in mind while writing your ducks modules. The rules of ducks are as follows, pulled from the repo's documentation: > A module... >1. MUST export default a function, the reducer 2. MUST export its action creators as functions 3. MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE 4. 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 When tackling the implementation of these elements, there is unavoidably a bit of boilerplate and code repetition, such as switch-case pattern for reducer and action creators payloads. There is a nice utility library that aims to reduce that boilerplate, [redux-actions](https://github.com/acdlite/redux-actions), which we'll use in this exemplification. Another library to keep in your toolbelt is [immutability-helper](https://github.com/kolodny/immutability-helper), which allows you to perform copy of data without changing the original source, in order to fulfill the 3rd principle of redux boiled down at the beginning of the section With all that, and I know you are eager to, let's see some code in place: ```javascript import { createAction, handleActions } from 'redux-actions'; import update from 'immutability-helper'; // Actions, action name: ${your-app-name}/${your-duck-name}/${_} -> For namespacing and sorting your reducers const PRODUCTS_REQUEST_FETCH = 'super-checkout/dummy/PRODUCTS_REQUEST_FETCH'; const PRODUCTS_SUCCESS_FETCH = 'super-checkout/dummy/PRODUCTS_SUCCESS_FETCH'; const MORE_PRODUCTS_LOAD = 'super-checkout/dummy/MORE_PRODUCTS_LOAD'; // Reducer, check https://github.com/kolodny/immutability-helper#available-commands to find out about $set, $push... const initalState = { isFetching: false, products: [], }; const handleFetchProductsRequest = state => update(state, { isFetching: { $set: true }, }); const handleFetchProductsSuccess = (state, { payload }) => update(state, { isFetching: { $set: false }, products: { $set: payload }, }); const handleLoadMoreProducts = (state, { payload }) => update(state, { isFetching: { $set: false }, products: { $push: payload }, }); export default handleActions({ [PRODUCTS_REQUEST_FETCH]: handleFetchProductsRequest, [PRODUCTS_SUCCESS_FETCH]: handleFetchProductsSuccess, [MORE_PRODUCTS_LOAD]: handleLoadMoreProducts, }, initalState); // Action Creators, action creator name: -> to clearly identify what type of function it is export const fetchProductsRequest = createAction(PRODUCTS_REQUEST_FETCH); export const fetchProductsSuccess = createAction(PRODUCTS_SUCCESS_FETCH); export const loadMoreProducts = createAction(MORE_PRODUCTS_LOAD); ``` Notes: - Why `handleActions` instead of the documented switch statement? Primarily because it keeps a clean switch-like syntax, while adding block scoping to the cases. This means you can reuse variable names in each β€œcase” - `createAction` creates a [flux-standard](https://github.com/acdlite/flux-standard-action) action creator. For instance, when we'll call `loadMoreProducts(products)` from our code, it'll return the action `{ type: 'super-checkout/dummy/MORE_PRODUCTS_LOAD', payload: products }`. ### React-redux [React-redux](https://github.com/reactjs/react-redux) comprises the official bindings that connects redux to React. Its API has merely two utilities, `` and `connect()`. We have already seen the first one when injecting the store into our React application and that's the only place where you'll ever use it. `connect` though, is a function that returns a HoC (you should be already familiar with the concept πŸ˜ƒ ). That HoC itself, returns a new, connected component class for you to use. That's why it needs to be invoked two times. The first time with some config arguments and a second time, with the component. We'll ilustrate it with a simple example: ```javascript import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import { toggleDummy } from '../ducks/dummy'; const DummyToggle = ({ isDummy, onToggleDummy }) => (
{isDummy ? 'Oh ma\'h, I am a dummy component! πŸ— ' : 'I am a clever component πŸ€“ '}
); DummyToggle.propTypes = { isDummy: PropTypes.bool.isRequired, onToggleDummy: PropTypes.func.isRequired, }; export default connect( state => ({ isDummy: state.dummy.isDummy, }), { onToggleDummy: toggleDummy, } )(DummyToggle); ``` The most important config [arguments](https://github.com/reactjs/react-redux/blob/master/docs/api.md#arguments) that connect receives are: - `mapStateToProps: Function`: takes a single argument of the entire Redux store’s state and returns an object to be passed as props. It is often called a selector. If this argument is specified, the new component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called. - `mapDispatchToProps: Function || Object`: If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly. For instance, when you click on the button, the onClick listener will fire `onToggleDummy()`, which in turn will result on `dispatch(onToggleDummy())` For more advance scenarios, check the [official API](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options)