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

React and Redux

React and Redux

Redux is FLUX inspired architecture which is becoming de-facto standard for building React applications. But way? The talk tries to explain that.

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

Radoslav Stankov

March 27, 2016
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. React & Redux
    Radoslav Stankov 27/03/2016

    View full-size slide

  2. Radoslav Stankov
    @rstankov

    http://rstankov.com

    http://github.com/rstankov

    View full-size slide

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

    View full-size slide

  4. 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 full-size slide

  5. render() {
    return (




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

    {todo.text}

    ))}


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


    );
    }

    View full-size slide

  6. 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 full-size slide

  7. 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 full-size slide

  8. 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 full-size slide

  9. });
    };
    }
    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 full-size slide

  10. Where is all the data?

    View full-size slide

  11. [todos] [filter]








    View full-size slide

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

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

    );
    }
    }

    View full-size slide

  13. 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 full-size slide

  14. Let’s add new action

    View full-size slide

  15. [todos] [filter]








    View full-size slide

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

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

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

    );
    }
    }

    View full-size slide

  17. 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 full-size slide

  18. Dispatcher
    Action
    View
    Store

    View full-size slide

  19. Dispatcher
    Action API
    View
    Store

    View full-size slide

  20. class TodosStore extends ReduceStore {
    getInitialState() {
    return [];
    }
    reduce (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:
    case 'todo/remove':
    return state.filter((t) => t.id !== action.id);
    case 'todo/clear':
    return state.filter((t) => !t.completed)
    default:
    return state;
    }
    }
    }

    View full-size slide

  21. const dispatcher = new Dispatcher();
    const store = new TodosStore(dispatcher);
    store.getState() // => []


    View full-size slide

  22. const dispatcher = new Dispatcher();
    const store = new TodosStore(dispatcher);
    store.getState() // => []

    dispatcher.dispatch({ type: 'todo/add' text: 'Task 1' });
    store.getState() // => [{ id: 1, text: 'Task 1', completed: false }]

    View full-size slide

  23. const dispatcher = new Dispatcher();
    const store = new TodosStore(dispatcher);
    store.getState() // => []

    dispatcher.dispatch({ type: 'todo/add' text: 'Task 1' });
    store.getState() // => [{ id: 1, text: 'Task 1', completed: false }]
    dispatcher.dispatch({ type: 'todo/toggle', id: 1 });
    store.getState() // => [{ id: 1, text: 'Task 1', completed: true }]

    View full-size slide

  24. const dispatcher = new Dispatcher();
    const store = new TodosStore(dispatcher);
    store.getState() // => []

    dispatcher.dispatch({ type: 'todo/add' text: 'Task 1' });
    store.getState() // => [{ id: 1, text: 'Task 1', completed: false }]
    dispatcher.dispatch({ type: 'todo/toggle', id: 1 });
    store.getState() // => [{ id: 1, text: 'Task 1', completed: true }]
    dispatcher.dispatch({ type: 'todo/remove', id: 1 });
    store.getState() // => []

    View full-size slide

  25. class FilterStore extends ReduceStore {
    getInitialState() {
    return { name: 'All', filter: Filters['All'] };
    }
    reduce (state, action) {
    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 full-size slide

  26. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  27. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  28. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  29. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  30. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  31. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

  32. import {Container} from 'flux/utils';
    class TodoList extends React.Component {
    static getStores() {
    return [TodosStore, FiltersStore];
    }
    static calculateState() {
    return {
    todos: TodosStore.getState(),
    filter: FiltersStore.getState().filter,
    };
    }
    render() {
    const todos = this.state.todos.filter(this.state.filter);
    return (

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

    );
    }
    }
    export default Container.create(TodoList);

    View full-size slide

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

    {todo.text}


    );
    }
    }

    View full-size slide

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

    {todo.text}


    );
    }
    }

    View full-size slide

  35. import {dispatch} from 'stores/dispatcher';

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

    View full-size slide

  36. import {dispatch} from 'stores/dispatcher';

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

    View full-size slide

  37. const dispatcher = new Dispatcher();
    const store = new TodosStore(dispatcher);

    View full-size slide

  38. • Global state
    • Communication between multiple stores
    • Dispatch locks
    • Testing
    Issues with FLUX

    View full-size slide

  39. Dispatcher
    Action
    View
    Store

    View full-size slide

  40. Dispatcher
    Action
    View
    Store Store

    View full-size slide

  41. Action
    View
    Store
    Reducer(s)
    Reducer(s)

    View full-size slide

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

    View full-size slide

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

    View full-size 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 // # => [ ]

    View full-size slide

  45. 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 full-size slide

  46. Store
    State: todo


    todoReducer
    State: filter


    filterReducer

    View full-size slide

  47. // reducers/index.js

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

    View full-size slide

  48. class TodosStore extends ReduceStore {
    getInitialState() {
    return [];
    }
    reduce (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:
    case 'todo/remove':
    return state.filter((t) => t.id !== action.id);
    case 'todo/clear':
    return state.filter((t) => !t.completed)
    default:
    return state;
    }
    }
    }

    View full-size slide

  49. // reducers/todos.js
    export default function(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 full-size slide

  50. // reducers/visibilityFilter.js
    export default function(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 full-size slide

  51. // reducers/index.js

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

    View full-size slide

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



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

    View full-size slide










  53. Store
    [todos] [filter]

    View full-size slide

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





    );
    }
    }

    View full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size 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 full-size 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 full-size slide

  62. Let's write some tests

    View full-size slide

  63. 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 full-size slide

  64. 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 full-size slide

  65. // Using: chai, enzyme, chai-enzyme
    import {TodoList} from 'components/TodoList';
    import {TodoItem} from 'components/TodoItem';
    describe(TodoList.dispalyName, () => {
    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 full-size slide

  66. 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 full-size slide


  67. 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 full-size slide

  68. 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 /

    View full-size slide

  69. https://speakerdeck.com/rstankov/react-and-redux

    View full-size slide

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

    View full-size slide

  71. @rstankov
    Thanks :)

    View full-size slide