Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Integrating Redux with React

Integrating Redux with React

Presentations walks through the steps of integrating redux in react project.

(Video: https://www.youtube.com/watch?v=3x6qGs1_aoU)

Radoslav Stankov

June 18, 2016
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. Integrating
    Redux with React
    Radoslav Stankov 18/0/2016

    View Slide

  2. Radoslav Stankov
    @rstankov

    http://rstankov.com

    http://github.com/rstankov

    View Slide

  3. View Slide

  4. View Slide

  5. https://github.com/rstankov/talks-code

    View Slide

  6. View Slide

  7. React

    View Slide

  8. View Slide

  9. const FILTERS = {
    All: () => true,
    Active: (t) => !t.completed,
    Completed: (t) => t.completed,
    };
    export default class App extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    filter: 'All',
    todos: [],
    };
    }
    render() {
    return (




    {this.filteredTodos().map((todo) => (

    View Slide

  10. render() {
    return (




    {this.filteredTodos().map((todo) => (

    {todo.text}

    ))}


    {this.counterText()}
    {Object.keys(FILTERS).map((filterName) => (
    ))}


    );
    }

    View Slide

  11. counterText() {
    const count = this.state.todos.filter((t) => !t.completed).length;
    return `${ count } ${ count === 1 ? 'item' : 'items' } left`;
    }
    filteredTodos() {
    return this.state.todos.filter(FILTERS[this.state.filter]);
    }
    showCompletedCount() {
    return this.todos.filter((t) => t.completed).length > 0;
    }
    handleNewTodo(text) {
    const todo = {
    id: +(new Date()),
    text: text,
    completed: false
    };
    this.setState({
    todos: [todo].concat(this.state.todos),
    });
    }

    View Slide

  12. filteredTodos() {
    return this.state.todos.filter(FILTERS[this.state.filter]);
    }
    showCompletedCount() {
    return this.todos.filter((t) => t.completed).length > 0;
    }
    handleNewTodo(text) {
    const todo = {
    id: +(new Date()),
    text: text,
    completed: false
    };
    this.setState({
    todos: [todo].concat(this.state.todos),
    });
    }
    handleClearCompleted() {
    this.setState({
    todos: this.state.todos.filter((t) => !t.completed),
    });
    }

    View Slide

  13. this.setState({
    todos: [todo].concat(this.state.todos),
    });
    }
    handleClearCompleted() {
    this.setState({
    todos: this.state.todos.filter((t) => !t.completed),
    });
    }
    removeTodoHandler(todo) {
    return (e) => {
    this.setState({
    todos: this.state.todos.filter((t) => t.id !== todo.id),
    });
    };
    }
    toggleTodoHandler(todo) {
    return (e) => {
    this.setState({
    todos: this.state.todos.map((t) => {
    if (t.id === todo.id) {

    View Slide

  14. });
    };
    }
    toggleTodoHandler(todo) {
    return (e) => {
    this.setState({
    todos: this.state.todos.map((t) => {
    if (t.id === todo.id) {
    t.completed = !t.completed;
    }
    return t;
    }),
    });
    };
    }
    filterHander(filterName) {
    return (e) => {
    this.setState({
    filter: filterName,
    });
    };
    }
    }

    View Slide

  15. View Slide

  16. View Slide










  17. View Slide










  18. View Slide










  19. View Slide










  20. View Slide










  21. View Slide










  22. View Slide










  23. View Slide










  24. View Slide

  25. Where is all the data?

    View Slide

  26. [todos] [filter]








    View Slide

  27. export default class App extends React.Component {
    // . . .
    render() {
    return (

    addTodo={this.addTodo} />
    todos={this.state.todos.filter(FILTERS[this.state.filter])}
    toggleTodo={this.toggleTodo}
    removeTodo={this.removeTodo} />
    todos={this.state.todos}
    filters={FILTERS}
    activeFilter={this.state.filter}
    changeFilter={this.changeFilter}
    clearCompletedTodos={this.clearCompletedTodos} />

    );
    }
    }

    View Slide

  28. export default class TodoList extends React.Component {
    static propTypes = {
    toggleTodo: React.PropTypes.func.isRequired,
    removeTodo: React.PropTypes.func.isRequired,
    todos: React.PropTypes.array.isRequired,
    };
    render() {
    return (

    {this.props.todos.map((todo) => (
    key={todo.id}
    todo={todo}
    toggleTodo={this.props.updateTodo}
    removeTodo={this.props.removeTodo} />
    ))}

    );
    }
    }

    View Slide

  29. View Slide

  30. Let’s add new action

    View Slide

  31. [todos] [filter]








    View Slide

  32. export default class App extends React.Component {
    // . . .
    render() {
    return (

    addTodo={this.addTodo} />
    todos={this.state.todos.filter(FILTERS[this.state.filter])}
    toggleTodo={this.toggleTodo}

    updateTodo={this.updateTodo}
    removeTodo={this.removeTodo} />
    todos={this.state.todos}
    filters={FILTERS}
    activeFilter={this.state.filter}
    changeFilter={this.changeFilter}
    clearCompletedTodos={this.clearCompletedTodos} />

    );
    }
    }

    View Slide

  33. export default class TodoList extends React.Component {
    static propTypes = {
    toggleTodo: React.PropTypes.func.isRequired,
    removeTodo: React.PropTypes.func.isRequired,
    updateTodo: React.PropTypes.func.isRequired,
    todos: React.PropTypes.array.isRequired,
    };
    render() {
    return (

    {this.props.todos.map((todo) => (
    key={todo.id}
    todo={todo}
    toggleTodo={this.props.updateTodo}
    toggleTodo={this.props.updateTodo}
    removeTodo={this.props.removeTodo} />
    ))}

    );
    }
    }

    View Slide

  34. View Slide

  35. View Slide

  36. Store

    View Slide

  37. React View
    Store

    View Slide

  38. Action
    React View
    Store

    View Slide

  39. Reducer
    Action
    React View
    Store

    View Slide

  40. Reducer
    Action
    React View
    New Store

    View Slide

  41. let store = {
    todos: [],
    filter: 'All',
    };

    View Slide

  42. let store = {
    todos: [],
    filter: 'All',
    };
    store = reduce(store, {action: 'todo/add', todo: todo});
    store.todos // # => [ todo ]

    View Slide

  43. let store = {
    todos: [],
    filter: 'All',
    };
    store = reduce(store, {action: 'todo/add', todo: todo});
    store.todos // # => [ todo ]
    store = reduce(store, {action: 'todo/toggle', id: todo.id});
    store = reduce(store, {action: 'todo/clear', id: todo.id});
    store.todos // # => [ ]

    View Slide

  44. let store = {
    todos: [],
    filter: 'All',
    };
    store = reduce(store, {action: 'todo/add', todo: todo});
    store.todos // # => [ todo ]
    store = reduce(store, {action: 'todo/toggle', id: todo.id});
    store = reduce(store, {action: 'todo/clear', id: todo.id});
    store.todos // # => [ ]
    store = reduce(store, {action: 'filter/select', name: 'Active'});
    store.filter // # => 'Active'

    View Slide

  45. import {createStore} from 'redux'

    let store = createStore(reduce)
    store.dispatch({action: 'todo/add', todo: todo});
    store.getState().todos // # => [ todo ]
    store.dispatch({action: 'todo/toggle', id: todo.id});
    store.dispatch({action: 'todo/clear', id: todo.id});
    store.getState().todos // # => [ ]
    store.dispatch({action: 'filter/select', name: 'Active'});
    store.getState().filter // # => 'Active'

    View Slide

  46. store.subscribe(function() {
    console.log('new state', store.getState());
    });

    View Slide

  47. import React from 'react'
    import {render} from 'react-dom'
    import {Provider} from 'react-redux'
    import {createStore} from 'redux'
    import reducer from './reducers'
    import App from './components/App'
    let store = createStore(reducer)
    let root = (



    );
    render(root, document.getElementById('root'));

    View Slide

  48. Action
    reducer state

    View Slide

  49. Action
    reducer state

    View Slide

  50. todoReducer
    Action
    reducer state

    View Slide

  51. todoReducer
    Action
    todos
    reducer state

    View Slide

  52. todoReducer
    Action
    todos
    filterReducer
    reducer state

    View Slide

  53. todoReducer
    Action
    todos
    filterReducer filter
    reducer state

    View Slide

  54. // reducers/todos.js
    export default function todoReducer(state = [], action) {
    switch (action.type) {
    case 'todo/add':
    return [action.todo].concat(state);
    case 'todo/toggle':
    return state.map((t) => t.id === action.id ? { ...t, completed: t
    case 'todo/remove':
    return state.filter((t) => t.id !== action.id);
    case 'todo/clear':
    return state.filter((t) => !t.completed)
    default:
    return state;
    }
    }

    View Slide

  55. // reducers/visibilityFilter.js
    export default function filterReducer(state, action) {
    if (!state) {
    state = DEFAULT_STATE;
    }
    switch (action.type) {
    case 'filter/select':
    const name = action.filterName;
    const filter = FILTERS[name];
    if (!filter) {
    return state;
    }
    return { name: name, filter: filter };
    default:
    return state;
    }
    }

    View Slide

  56. // reducers/index.js

    import {combineReducers} from 'redux'
    import todos from './todos'
    import visibilityFilter from './visibilityFilter'
    export default combineReducers({
    todos: todos,
    visibilityFilter: visibilityFilter,
    });

    View Slide

  57. import React from 'react'
    import {render} from 'react-dom'
    import {Provider} from 'react-redux'
    import {createStore} from 'redux'
    import reducer from './reducers'
    import App from './components/App'
    let store = createStore(reducer)
    let root = (



    );
    render(root, document.getElementById('root'));

    View Slide










  58. Store
    [todos] [filter]

    View Slide










  59. View Slide

  60. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  61. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  62. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  63. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  64. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  65. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  66. import {connect} from 'react-redux'
    class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  67. Let's write some tests

    View Slide

  68. import {connect} from 'react-redux'
    export class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  69. import {connect} from 'react-redux'
    export class TodoList extends React.Component {
    render() {
    const todos = this.props.todos.filter(this.props.filter);
    return (

    {todos.map((todo) => )}

    );
    }
    }
    const decorate = connect((state) => {
    return {
    todos: state.todos,
    filter: state.visibilityFilter.filter,
    };
    });
    export default decorate(TodoList);

    View Slide

  70. // Using: chai, enzyme, chai-enzyme
    import {TodoList} from 'components/TodoList';
    import {TodoItem} from 'components/TodoItem';
    describe(TodoList.name, () => {
    const all = () => true
    const none = () => false
    it("renders list of TodoItem(s)", () => {
    const todos = [fakeTodo()];

    const element = shallow();

    expect(element).to.have.descendants();
    });
    it("filters with filter", () => {
    const todos = [fakeTodo()];
    const element = shallow();
    expect(element).to.have.descendants();
    });
    });

    View Slide










  71. View Slide

  72. export class TodoItem extends React.Component {
    static propTypes = {
    todo: React.PropTypes.object.isRequired,
    dispatch: React.PropTypes.func.isRequired,
    };
    handleRemove() {
    this.props.dispatch(removeTodo(this.props.todo));
    }
    handleToggle() {
    this.props.dispatch(toggleTodo(this.props.todo));
    }
    render() {
    return (

    {this.props.todo.text}


    );
    }
    }
    export default connect()(TodoItem);

    View Slide


  73. export function toggleTodo(todo) {
    return {
    type: 'todo/toggle',
    id: todo.id,
    };
    }
    export function removeTodo(todo) {
    return {
    type: 'todo/remove',
    id: todo.id,
    };
    }
    export function clearCompletedTodos() {
    return {
    type: 'todo/clear',
    };
    }

    View Slide

  74. View Slide

  75. Redux friends
    • selectors - https://github.com/reactjs/reselect
    • async - https://github.com/gaearon/redux-thunk
    • router - https://github.com/reactjs/react-router-redux
    • data fetching - https://github.com/relax/relate
    • store - https://github.com/elgerlambert/redux-localstorage
    • logger - https://github.com/evgenyrodionov/redux-logger

    View Slide

  76. https://speakerdeck.com/rstankov/integrating-redux-with-react

    View Slide

  77. https://github.com/rstankov/talks-code

    View Slide

  78. View Slide

  79. @rstankov
    Thanks :)

    View Slide

  80. View Slide