TLTR; Redux

Think state as a repository in the memory which stores data from a database, an api, the local cache, a UI element in the screen like a form field and more.

How It Works

{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}]
}

Actions

{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }

Action Creators

function addTodo(text = '') {  return { type: 'ADD_TODO', text }}function toggleTodo(index = 0) {  return { type: 'TOGGLE_TODO', index }}

Reducers

function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map(
(todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
files: files(state.files, action),
folders: folders(state.folders, action),
notifications: notifications(state.notifications, action),
filter: filter(state.filter, action),
searchField: searchField(state.searchField, action),
...
}
}

Store

import { createStore } from 'redux'
import todoApp from './reducers'
// create the store
let store = createStore(todoApp)
// read the state object
store.getState()
// dispatch actions that eventually modify the state
store.dispatch(addTodo('Learn about actions'))
store.dispatch(toggleTodo(0))
// log state to the console every time it gets updated
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// stop listening to state updates
unsubscribe()

The Famous Three Principles

How To Use Redux With React

Redux works great with libraries like React. Every time you have a state mutation, the root component gets the update and re-renders the UI. Pretty simple right?

Install 👩‍💻

npm install --save react-redux

Provide 🤗

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'
import store from './pathToStore'

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

Connect 👪

import { connect } from 'react-redux'
import TodoList from './TodoList'
const TodoListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

export default TodoListContainer
const mapStateToProps = state => {
return {
todos: state.todos
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
TodoList.propTypes = {
onTodoClick: PropTypes.func.isRequired,
todos: PropTypes.object.isRequired
}

Enjoy 🍾

  1. Every time something happens you call an action
  2. The reducers decide which mutations they have to apply to their state
  3. Your containers get the updated state and they pass the data down to the presentational components

Async Redux

Redux is synchronous by default. There are multiple ways to add async behaviour.

Async Actions

  • FETCH_POSTS_REQUEST: The request has began. You may store in your state a flag isFetching and if it’s true to show a loading indicator.
  • FETCH_POSTS_SUCCESS: The request has finished successfully. Change the flag isFetching to false, remove the loader and show the posts.
  • FETCH_POSTS_FAILURE: The request has failed. Change the flag isFetching to false, store and display the error in the screen.

Async Action Creators

export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}

export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
return {
type: RECEIVE_POSTS,
subreddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}

// This is a thunk action creator!
export function fetchPosts(subreddit) {
return function (dispatch) {
// dispatch the request
dispatch(requestPosts(subreddit))

return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
error => console.log('An error occured.', error)
)
.then(json =>
dispatch(receivePosts(subreddit, json))
)
}
}

Middleware

Middleware is some code you can put between the framework receiving a request, and the framework generating a response.

import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import { selectSubreddit, fetchPosts } from './actions'
import rootReducer from './reducers'

const loggerMiddleware = createLogger()

const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
)

DevTools

Inspect every single action and understand what happened.
Go back in time and replay every single action.

Testing

Because most of the Redux code you write are functions, and many of them are pure, they are easy to test without mocking.

import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'

describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish docs'
const expectedAction = {
type: types.ADD_TODO,
text
}
expect(actions.addTodo(text)).toEqual(expectedAction)
})
})
describe('todos reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual([
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})

it('should handle ADD_TODO', () => {
expect(
reducer([], {
type: types.ADD_TODO,
text: 'Run the tests'
})
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}
])

expect(
reducer(
[
{
text: 'Use Redux',
completed: false,
id: 0
}
],
{
type: types.ADD_TODO,
text: 'Run the tests'
}
)
).toEqual([
{
text: 'Run the tests',
completed: false,
id: 1
},
{
text: 'Use Redux',
completed: false,
id: 0
}
])
})
})

Tips & Tricks

  • Instead of Object.assign() you can use the spread operator to simplify your reducers:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return { ...state, visibilityFilter: action.filter }
default:
return state
}
}

Resources

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nicos Tsourektsidis

Nicos Tsourektsidis

Lead Software Engineer @ Epam Systems Switzerland. I ❤️ JavaScript, React, User Interfaces, coffee and video games.