135
Managing Data with React and Redux 1

Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

  • Upload
    others

  • View
    7

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Managing Data with React and Redux

1

Page 2: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

@Jack_Franklin@pusher

2

Page 3: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

• Code, notes, etc: github.com/jackfranklin/react-redux-talk

• Slides (after talk): speakerdeck.com/jackfranklin

• I'll tweet them all: twitter.com/jack_franklin

3

Page 4: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

4

Page 5: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

5

Page 6: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

In the beginning

6

Page 7: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

export default class Todos extends React.Component {

constructor(props) {

super(props);

this.state = {

todos: [

{ id: 1, name: 'Write the blog post', done: false },

{ id: 2, name: 'Buy Christmas presents', done: false },

{ id: 3, name: 'Leave Santa his mince pies', done: false },

]

}

}

...

}

7

Page 8: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

render() {

return (

<div>

<p>The <em>best</em> todo app out there.</p>

<h1>Things to get done:</h1>

<ul className="todos-list">{ this.renderTodos() }</ul>

<AddTodo onNewTodo={(todo) => this.addTodo(todo)} />

</div>

)

}

8

Page 9: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

<AddTodo onNewTodo={(todo) => this.addTodo(todo)} />

9

Page 10: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Parent component contains all state.

Child components are given functions to call to tell the parent component of the new state.

10

Page 11: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js contained the logic for updating the state from some user input.

constructor(props) {...}

addTodo(todo) {

const newTodos = this.state.todos.concat([todo]);

this.setState({ todos: newTodos });

}

...

render() {...}

11

Page 12: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

But then as this component grew I pulled out the business logic into standalone JavaScript functions:

12

Page 13: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

constructor(props) {...}

addTodo(todo) {

this.setState(addTodo(this.state, todo));

}

...

render() {...}

13

Page 14: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

State functions can take the current state and produce a new state.

export function deleteTodo(state, id) {

return {

todos: state.todos.filter((todo) => todo.id !== id)

};

}

14

Page 15: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

This is effectively a very, very basic Redux (but worse in many ways!).

15

Page 16: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

This is fine for small applications, but it tightly couples components and makes refactoring or restructuring components trick and makes refactoring or restructuring components tricky.

16

Page 17: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The more data you have, the more difficult it is to manage as different components can edit different pieces of data.

17

Page 18: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

If you split the data up across components, you no longer have a single source of truth for your application's data.

18

Page 19: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

It's tricky to track down what caused the data to change, and where it happened.

grep setState

19

Page 20: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

As your application grows you need some process and structure around your data.

20

Page 21: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

But don't use Redux by default! For smaller apps you'll probably find yourself quite content without.

21

Page 22: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Redux

22

Page 23: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The three principles of Redux.• Single Source of Truth: all data is stored in

one object.

• State is read only: nothing can directly mutate the state.

• The state is manipulated by pure functions: no external data can affect them.

23

Page 24: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Building a Redux application

24

Page 25: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

import { createStore } from 'redux';

function counter(state, action) {

...

}

const store = createStore(counter);

console.log('Current state', store.getState());

25

Page 26: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

• store: the object that holds our state

• action: an object sent to the store in order to manipulate the store's data

• reducer: a function that takes the current state, an action and produces the new state.

26

Page 27: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

First: define your actions.

{ type: 'INCREMENT' }

{ type: 'DECREMENT' }

27

Page 28: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Second: define how your reducer should deal with those actions:

function counter(state, action) {

switch (action.type) {

case 'INCREMENT':

return state + 1;

case 'DECREMENT':

return state - 1;

default:

return state;

}

}

28

Page 29: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Third: define what to do if you don't have any state:

function counter(state, action) { if (!state) state = 0; ...}

// ORfunction counter(state = 0, action) { ...}

29

Page 30: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Fourth: create your store and dispatch some actions:

const store = createStore(counter);

store.dispatch({ type: 'INCREMENT' });

console.log('Current state', store.getState());

// => 1

30

Page 31: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

What makes this good?• The main logic is contained in a function,

abstracted away from the store. It's easy to modify, follow and test.

• Nothing ever manipulates the state, all manipulation is done via actions.

• Actions are just plain objects; they can be logged, serialised, repeated, and so on.

• Our reducer is pure - the state is never 31

Page 32: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Adding Redux to a React applicationnpm install --save redux react-redux

32

Page 33: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

First, let's decide what our state will look like:

{

todos: [

{ id: 1, name: 'buy milk', done: false },

...

]

}

33

Page 34: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Secondly, let's define the actions.

• { type: 'ADD_TODO', name: '...' }

• { type: 'DELETE_TODO', id: ... }

• { type: 'TOGGLE_TODO', id: ... }

34

Page 35: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Thirdly, let's define the reducer function that will deal with these actions.

export default function todoAppReducers(

state = { todos: [] },

action

) {

...

};

35

Page 36: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We can first deal with ADD_TODO:

switch (action.type) { case 'ADD_TODO': const todo = { name: action.name, id: state.todos.length, done: false }

return { todos: state.todos.concat([todo]) }}

36

Page 37: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And then DELETE_TODO:

case 'DELETE_TODO': return { todos: state.todos.filter((todo) => todo.id !== action.id) }

37

Page 38: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And finally TOGGLE_TODO:

case 'TOGGLE_TODO': const todos = state.todos.map((todo) => { if (todo.id === action.id) { todo.done = !todo.done; }

return todo; });

return { todos };38

Page 39: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We've just modelled most of our business logic without having to deal with UI interactions or anything else.

This is one of the biggest pluses to using Redux.

39

Page 40: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Now let's create a store and connect our components.

40

Page 41: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

By default a component does not have access to the store.

Components that do are known as "smart" components.

Components that do not are known as "dumb" components.

41

Page 42: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/index.js

import React from 'react';

import { render } from 'react-dom';

import Todos from './todos';

class AppComponent extends React.Component {

render() {

return <Todos />;

}

}

render(

<AppComponent />,

document.getElementById('app')

);

42

Page 43: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We'll firstly create a store and connect our application.

// other imports skipped

import { Provider } from 'react-redux';

import { createStore } from 'redux';

import todoAppReducers from './reducers';

const store = createStore(todoAppReducers);

class AppComponent extends React.Component {...}

render(

<Provider store={store}>

<AppComponent />

</Provider>,

document.getElementById('app')

);

43

Page 44: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

All the Provider does is make components in our application able to connect to the store if we give them permission.

You only need to wrap your top level component in the Provider, and once it's done you can mostly forget about it.

44

Page 45: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We now have a store and our top level component has been given access to it.

Now we need to give our components access to the store so they can render the data to it.

45

Page 46: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The Todos component needs access to the todos in the store so it can render the individual todos.

We can use the connect function from react-redux to do this.

46

Page 47: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

import { connect } from 'react-redux';

class Todos extends React.Components {...};

const ConnectedTodos = connect((state) => {

return {

todos: state.todos

}

})(Todos);

47

Page 48: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The connect function takes a React component and produces a new component that will be given access to parts of the state as props.

This lets us strictly control which parts of our state each component has access to.

It also provides this.props.dispatch, which is used to dispatch actions, which we'll see later.

48

Page 49: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

So now within our Todos component we can swap this.state.todos to this.props.todos.

We can also get rid of all the functions for managing state, and stop passing them through to child components.

49

Page 50: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

class Todos extends React.Component {

renderTodos() {

return this.props.todos.map((todo) => {

return <li key={todo.id}><Todo todo={todo} /></li>;

});

}

render() {

return (

<div>

<ul className="todos-list">{ this.renderTodos() }</ul>

<AddTodo />

</div>

)

}

}

50

Page 51: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Notice how much cleaner this is, and how our component is purely focused on presentation.

51

Page 52: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Now let's hook up AddTodo so we can create new todos.

52

Page 53: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

AddTodo doesn't need to access any data in the store but it does need to dispatch actions, so it too must be connected.

class AddTodo extends React.Component {...};

const ConnectedAddTodo = connect()(AddTodo);

export default ConnectedAddTodo;

53

Page 54: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The old addTodo method:

addTodo(e) {

e.preventDefault();

const newTodoName = this.refs.todoTitle.value;

if (newTodoName) {

this.props.onNewTodo({

name: newTodoName

});

this.refs.todoTitle.value = '';

}

}

54

Page 55: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The new one:

addTodo(e) { e.preventDefault(); const newTodoName = this.refs.todoTitle.value; if (newTodoName) { this.props.dispatch({ name: newTodoName, type: 'ADD_TODO' }); ... }}

55

Page 56: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And we're now using Redux to add Todos!

56

Page 57: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Finally, we can update the Todo component to dispatch the right actions for toggling and deleting.

57

Page 58: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

toggleDone() { this.props.dispatch({ type: 'TOGGLE_TODO', id: this.props.todo.id });}

deleteTodo(e) { this.props.dispatch({ type: 'DELETE_TODO', id: this.props.todo.id });}

58

Page 59: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

But there's a problem!

59

Page 60: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Mutation!

60

Page 61: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Redux expects you to never mutate anything, and if you do it can't always correctly keep your UI in sync with the state.

We've accidentally mutated...

61

Page 62: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

In our reducer...

case 'TOGGLE_TODO': const todos = state.todos.map((todo) => { if (todo.id === action.id) { todo.done = !todo.done; }

return todo; });

return { todos };62

Page 63: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

todo.done = !todo.done;

63

Page 64: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

A quick rewrite...

case 'TOGGLE_TODO': const todos = state.todos.map((todo) => { if (todo.id === action.id) { return { name: todo.name, id: todo.id, done: !todo.done } }

return todo; });

64

Page 65: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And it all works, as does deleting a todo. We're fully Reduxed!

65

Page 66: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Deep breath...

66

Page 67: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

That probably felt like a lot of effort, but the good news is once you've set Redux up you are set.

67

Page 68: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

1.Decide the shape of your state.

2.Decide the actions that can update the state.

3.Define your reducers that deal with actions.

4.Wire up your UI to dispatch actions.

5.Connect your components to the store to allow them to render state.

68

Page 69: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Still to come1.Debugging Redux

2.Better Redux Reducers

3.Middlewares

4.Async actions

69

Page 70: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

70

Page 71: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

If you're not using Chrome you can still use the devtools but it takes a bit more effort.

See: https://github.com/gaearon/redux-devtools

71

Page 72: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Firstly, install the plugin in Chrome.

72

Page 73: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Secondly, update the call to createStore:

createStore(reducers, initialState, enhancer).

Enhancer: a function that enhances the store with middleware or additional functionality. We'll see this again later.

73

Page 74: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

const store = createStore(

todoAppReducers,

undefined,

window.devToolsExtension ? window.devToolsExtension() : undefined

);

We leave the initialState as undefined because our reducer deals with no state.

74

Page 75: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

75

Page 76: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

76

Page 77: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

77

Page 78: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Better Reducers

78

Page 79: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Imagine our TODO app now needs to have a user log in first, and our state will now keep track of the user that's logged in.

79

Page 80: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Our new state will look like:

{

todos: [

{ id: 1, name: 'buy milk', done: false },

...

],

user: {

id: 123,

name: 'Jack'

}

}

80

Page 81: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Next, let's define some new actions:

{ type: 'LOG_USER_IN', id: ..., name: '...' }{ type: 'LOG_USER_OUT' }

81

Page 82: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And then our reducer needs to be updated. Firstly, now we have two keys in our state, we should update the reducers we have to not lose any keys they don't deal with.

82

Page 83: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Before:

case 'DELETE_TODO': return { todos: state.todos.filter((todo) => todo.id !== action.id) }

After:

case 'DELETE_TODO': return Object.assign({}, state, { todos: state.todos.filter((todo) => todo.id !== action.id) });

83

Page 84: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And now we can add reducers for the new user actions:

case 'LOG_USER_IN': return Object.assign({}, state, { user: { id: action.id, name: action.name } });

case 'LOG_USER_OUT': return Object.assign({}, state, { user: {} });

84

Page 85: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

export default function todoAppReducers( state = { todos: [initialTodo], user: {} }, action) { switch (action.type) { case 'ADD_TODO': const todo = { name: action.name, id: state.todos.length, done: false }

return Object.assign({}, state, { todos: state.todos.concat([todo]) });

case 'DELETE_TODO': return Object.assign({}, state, { todos: state.todos.filter((todo) => todo.id !== action.id) });

case 'TOGGLE_TODO': const todos = state.todos.map((todo) => { if (todo.id === action.id) { return { name: todo.name, id: todo.id, done: !todo.done } }

return todo; });

return Object.assign({}, state, { todos });

case 'LOG_USER_IN': return Object.assign({}, state, { user: { id: action.id, name: action.name } });

case 'LOG_USER_OUT': return Object.assign({}, state, { user: {} });

default: return state;

}};

85

Page 86: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Our reducer is huge, and deals with two different data sets:

• User

• Todos

In a larger app this will quickly become impossible to manage.

86

Page 87: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Instead we split into multiple reducers who are each resopnsible for a specific key in the state.

Each of these reducers is only given their part of the state.

87

Page 88: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

function userReducer(user = {}, action) { switch (action.type) { case 'LOG_USER_IN': return { id: action.id, name: action.name } case 'LOG_USER_OUT': return {};

default: return user; }}

88

Page 89: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

function todoReducer(todos = [], action) { switch (action.type) { case 'ADD_TODO': ...

case 'DELETE_TODO': return todos.filter((todo) => todo.id !== action.id);

case 'TOGGLE_TODO': ...

default: return todos; }}

89

Page 90: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And our main reducer function becomes:

export default function todoAppReducers(state = {}, action) {

return {

todos: todoReducer(state.todos, action),

user: userReducer(state.user, action)

}

};

90

Page 91: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Now as our state grows we'll add new functions for each key.

Turns out this pattern is so useful that Redux provides a method to do it for us: combineReducers.

91

Page 92: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Before:

export default function todoAppReducers(state = {}, action) {

return {

todos: todoReducer(state.todos, action),

user: userReducer(state.user, action)

}

};

92

Page 93: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

After:

import { combineReducers } from 'redux';

...

const todoAppReducers = combineReducers({

todos: todoReducer,

user: userReducer

});

export default todoAppReducers;

93

Page 94: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Deep breath again!

94

Page 95: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Middlewares

95

Page 96: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

You might be familiar with Rack Middlewares, NodeJS / Express middlewares, and so on.

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer

-- http://redux.js.org/docs/advanced/Middleware.html

96

Page 97: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

A middleware provides a function that is given the store object.

It should return another function that is called with next, the function it should call when it is finished.

That function should return another function that is called with the current action, action.

97

Page 98: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

WHAT?!

98

Page 99: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

const myMiddleware = function(store) {

return function(next) {

return function(action) {

// your logic goes here

// this is the dispatch function

// that each middleware can return

}

}

}

const myMiddleware = store => next => action => {

// your logic here

}

99

Page 100: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Each middleware replaces the dispatch function with its own function, and Redux chains them together correctly.

100

Page 101: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Example: logging each action

101

Page 102: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/middlewares.js

export const logMiddleware = store => next => action => {

console.log('MIDDLEWARE: About to dispatch', action);

return next(action);

};

102

Page 103: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/index.js

import { createStore, applyMiddleware } from 'redux';

...

const store = createStore(

todoAppReducers,

undefined,

applyMiddleware(logMiddleware)

);

103

Page 104: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

104

Page 105: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

But wait, we lost the devtools code!

105

Page 106: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

compose

106

Page 107: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

const store = createStore(

todoAppReducers,

undefined,

compose(

applyMiddleware(logMiddleware),

window.devToolsExtension ? window.devToolsExtension() : f => f

)

);

107

Page 108: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Final deep breath!

108

Page 109: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Async Actions

109

Page 110: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Up until now we've dealt purely with synchronous actions, but often your action will be async, most commonly, data fetching.

110

Page 111: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We could write our own middleware or logic to deal with this, but one already exists: redux-thunk.

npm install --save redux-thunk

111

Page 112: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

With thunk, functions can either return an action or another function that can dispatch actions.

112

Page 113: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The HTTP request cycle:

• Make the request, and dispatch action.

• Get the data back, and dispatch an action with that data.

• Request errors, dispatch an action with information about the error.

When you want to dispatch an action to cause an HTTP request you actually want to trigger multiple actions.

113

Page 114: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

export default function thunkMiddleware({ dispatch, getState }) {

return next => action => {

if (typeof action === 'function') {

return action(dispatch, getState);

}

return next(action);

};

}

114

Page 115: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Fake API:

export function fetchTodos() {

return new Promise((resolve, reject) => {

resolve({

todos: [{

id: 1,

name: 'Buy Milk',

done: false

}]

})

});

}

115

Page 116: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

We need some new information on the state:

{

todos: ...,

user: ...,

isFetching: true / false

}

116

Page 117: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And some new actions:

{ type: 'REQUEST_TODOS_INIT' },{ type: 'REQUEST_TODOS_SUCCESS', todos: [...] }// and one for error handling// if this were real

117

Page 118: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

function isFetchingReducer(isFetching = false, action) { switch (action.type) { case 'REQUEST_TODOS_INIT': return true;

case 'REQUEST_TODOS_SUCCESS': return false

default: return isFetching }}

118

Page 119: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

const todoAppReducers = combineReducers({

todos: todoReducer,

user: userReducer,

isFetching: isFetchingReducer

});

119

Page 120: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Action Creatorsapp/action-creators.js

export function fetchTodosAction() {

return (dispatch) => {

}

}

120

Page 121: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

import { fetchTodosAction } from './action-creators';

class Todos extends React.Component {

componentWillMount() {

this.props.dispatch(fetchTodosAction());

}

...

}

121

Page 122: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

First, dispatch REQUEST_TODOS_INIT:

export function fetchTodosAction() { return (dispatch) => { dispatch({ type: 'REQUEST_TODOS_INIT' }); }}

122

Page 123: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Then, fetch and dispatch REQUEST_TODOS_SUCCESS:

fetchTodos().then((data) => { dispatch({ type: 'REQUEST_TODOS_SUCCESS', todos: data.todos });});

123

Page 124: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

import { fetchTodos } from './fake-api';

export function fetchTodosAction() { return (dispatch) => { dispatch({ type: 'REQUEST_TODOS_INIT' });

fetchTodos().then((data) => { dispatch({ type: 'REQUEST_TODOS_SUCCESS', todos: data.todos }); }); }}

124

Page 125: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

app/todos.js

Within render:

{ this.props.isFetching && <p>LOADING...</p> }

Allow it access:

const ConnectedTodos = connect((state) => {

return {

todos: state.todos,

isFetching: state.isFetching

};

})(Todos);

125

Page 126: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Now we need our todosReducer to deal with the success action and use the data.

case 'REQUEST_TODOS_SUCCESS': return action.todos;

126

Page 127: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Et voila (the delay is me for effect!):

127

Page 128: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

And with that we now have support for async!

128

Page 129: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Housekeeping Actions

129

Page 130: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

The strings for our actions are cropping up in a lot of places.

Better to have them as constants that can be exported.

export const LOG_USER_IN = 'LOG_USER_IN';export const LOG_USER_OUT = 'LOG_USER_OUT';

That way you can keep things in sync easier.

130

Page 131: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Use action creators for creating actions:

export function addTodo(name) { return { type: ADD_TODO, name }};

131

Page 132: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

For more info: - http://redux.js.org/docs/basics/Actions.html- http://redux.js.org/docs/recipes/ReducingBoilerplate.html

132

Page 133: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

You've made it!Redux is tough to get started with but the benefits in a large application are huge.

• Code, notes, etc: github.com/jackfranklin/react-redux-talk

• Slides (after talk): speakerdeck.com/jackfranklin

• I'll tweet them all: twitter.com/jack_franklin

133

Page 134: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

Thanks :)

• @Jack_Franklin

• javascriptplayground.com

134

Page 135: Managing Data with React and Redux - SDD Conferencesddconf.com/brands/sdd/library/presentation.pdf · app/todos.js export default class Todos extends React.Component {constructor(props)

135