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. 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 ( <div className="app"> <div className="arrow"> <Input onSave={this.handleNewTodo.bind(this)} /> <ul className="list"> {this.filteredTodos().map((todo) => ( <li key={todo.id} className={todo.completed ? 'completed' : ''}> <input className="toggle" type="checkbox" checked={todo.complete
  2. render() { return ( <div className="app"> <div className="arrow"> <Input onSave={this.handleNewTodo.bind(this)}

    /> <ul className="list"> {this.filteredTodos().map((todo) => ( <li key={todo.id} className={todo.completed ? 'completed' : ''}> <input className="toggle" type="checkbox" checked={todo.complete <label>{todo.text}</label> <button className="destroy" onClick={this.removeTodoHandler(todo </li> ))} </ul> <div className="footer"> <div className="counter">{this.counterText()}</div> {Object.keys(FILTERS).map((filterName) => ( <button className={filterName === this.state.filter ? 'selected' : ))} <button className="clear" style={{visibility: this.showCompletedCoun </div> </div> ); }
  3. 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), }); }
  4. 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), }); }
  5. 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) {
  6. }); }; } 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, }); }; } }
  7. export default class App extends React.Component { // . .

    . render() { return ( <div className="app"> <NewTodo addTodo={this.addTodo} /> <TodoList todos={this.state.todos.filter(FILTERS[this.state.filter])} toggleTodo={this.updateTodo} removeTodo={this.removeTodo} /> <Footer todos={this.state.todos} filters={FILTERS} activeFilter={this.state.filter} changeFilter={this.changeFilter} clearCompletedTodos={this.clearCompletedTodos} /> </div> ); } }
  8. 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 ( <ul className="list"> {this.props.todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} toggleTodo={this.props.updateTodo} removeTodo={this.props.removeTodo} /> ))} </ul> ); } }
  9. export default class App extends React.Component { // . .

    . render() { return ( <div className="app"> <NewTodo addTodo={this.addTodo} /> <TodoList todos={this.state.todos.filter(FILTERS[this.state.filter])} toggleTodo={this.updateTodo}
 updateTodo={this.updateTodo} removeTodo={this.removeTodo} /> <Footer todos={this.state.todos} filters={FILTERS} activeFilter={this.state.filter} changeFilter={this.changeFilter} clearCompletedTodos={this.clearCompletedTodos} /> </div> ); } }
  10. 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 ( <ul className="list"> {this.props.todos.map((todo) => ( <TodoItem key={todo.id} todo={todo} toggleTodo={this.props.updateTodo} toggleTodo={this.props.updateTodo} removeTodo={this.props.removeTodo} /> ))} </ul> ); } }
  11. 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; } } }
  12. 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 }]
  13. 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 }]
  14. 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() // => []
  15. 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; } } }
  16. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  17. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  18. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  19. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  20. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  21. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  22. 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 ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } export default Container.create(TodoList);
  23. 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 ( <li className={todo.completed ? 'completed' : ''}> <CheckBox checked={todo.completed} onClick={this.handleToggle.bind(thi <label>{todo.text}</label> <DeleteButton onClick={this.handleRemove.bind(this)} /> </li> ); } }
  24. 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 ( <li className={todo.completed ? 'completed' : ''}> <CheckBox checked={todo.completed} onClick={this.handleToggle.bind(thi <label>{todo.text}</label> <DeleteButton onClick={this.handleRemove.bind(this)} /> </li> ); } }
  25. 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', }); }
  26. 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', }); }
  27. let store = { todos: [], filter: 'All', }; store

    = reduce(store, {action: 'todo/add', todo: todo}); store.todos // # => [ todo ]
  28. 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 // # => [ ]
  29. 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'
  30. // reducers/index.js
 import {combineReducers} from 'redux' import todos from './todos'

    import visibilityFilter from './visibilityFilter' export default combineReducers({ todos: todos, visibilityFilter: visibilityFilter, });
  31. 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; } } }
  32. // 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; } }
  33. // 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; } }
  34. // reducers/index.js
 import {combineReducers} from 'redux' import todos from './todos'

    import visibilityFilter from './visibilityFilter' export default combineReducers({ todos: todos, visibilityFilter: visibilityFilter, });
  35. 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 = ( <Provider store={store}> <App /> </Provider> ); render(root, document.getElementById('root'));
  36. export default class App extends React.Component { render() { return

    ( <div className="app"> <NewTodo /> <TodoList /> <Footer /> </div> ); } }
  37. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  38. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  39. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  40. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  41. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  42. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  43. import {connect} from 'react-redux' class TodoList extends React.Component { render()

    { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  44. import {connect} from 'react-redux' export class TodoList extends React.Component {

    render() { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  45. import {connect} from 'react-redux' export class TodoList extends React.Component {

    render() { const todos = this.props.todos.filter(this.props.filter); return ( <ul className="list"> {todos.map((todo) => <TodoItem key={todo.id} todo={todo} />)} </ul> ); } } const decorate = connect((state) => { return { todos: state.todos, filter: state.visibilityFilter.filter, }; }); export default decorate(TodoList);
  46. // 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(<TodoList todos={todos} filter={all} />); 
 expect(element).to.have.descendants(<TodoItem />); }); it("filters with filter", () => { const todos = [fakeTodo()]; const element = shallow(<TodoList todos={todos} filter={none} />); expect(element).to.have.descendants(<TodoItem />); }); });
  47. 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 ( <li className={this.props.todo.completed ? 'completed' : ''}> <CheckBox checked={this.props.todo.completed} onClick={this.handleTogg <label>{this.props.todo.text}</label> <DeleteButton onClick={this.handleRemove.bind(this)} /> </li> ); } } export default connect()(TodoItem);
  48. 
 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', }; }
  49. 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 /