Django Rest Framework and React and Redux, Oh My!
I’m Eric Palakovich Carr. Co-Founder & Chief Architect at Previously:
What this talk is not • A comprehensive tutorial • A deep dive into how these technologies works • You WILL need to go and learn this stuff on your own after this talk
{% extends "base.html" %} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <!-- more content in Django template --> {% endblock %}
Javascript Ecosystem • We’ll be using NPM to manage your packages • We’ll be using webpack to handle bundling our assets. • We’ll be using scotch to burn away the memories of configuring the build system for our project.
in a nutshell • The pip & cheeseshop / pypi of javascript • packages.json works like requirements.txt & setup.py • `npm init` in directory with packages.json generates node_modules. Kinda like a virtualenv directory. • packages.json also can act like your `manage.py` for your javascript code, but you populate it with custom commands.
Building for Javascript • Start coding your project, using `npm install some-package — save` as you go. This creates and maintains your package.json. • Setup the config file for your build tool (webpack.config.js, gulpfile.js, Gruntfile, etc) • Add configs for Babel, minification, and other JS stuff • Add configs LESS, SASS, and other CSS stuff • Plus source mapping, tests, linters, sprite sheets, etc • Run your tool to generate bundles, having them save into your static directory for Django to pick up.
{% extends "base.html" %} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "bundle.css" %}"/> <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <script src="{% static "bundle.js" %}"></script> <!-- more content in Django template --> {% endblock %}
{% extends "base.html" %} {% load staticfiles %} {% load render_bundle from webpack_loader %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> {% render_bundle 'main' 'js' %} <!-- more content in Django template --> {% endblock %}
DEMO
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') );
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') ); JSX
// TodoHeader.jsx import React, { Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
JSX // TodoHeader.jsx import React, { Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
// TodoHeader.jsx import React, { Component } from 'react'; export default class TodoHeader extends Component { render() { return React.createElement( "header", {className: "todoHeadCmp"}, React.createElement( "h1", null, this.props.listName, ) ); } }
export default class TodoTextInput extends Component { constructor(props, context) { super(props, context); this.state = { text: this.props.text || '' }; } handleSubmit(e) { const text = e.target.value.trim(); if (e.which === 13) { this.props.onSave(text); } } handleChange(e) { this.setState({ text: e.target.value }); } render() { return ( <input type='text' value={this.state.text} onChange={::this.handleChange} onKeyDown={::this.handleSubmit} /> ); } }
React Component Lifecycle • componentWillMount • componentDidMount • componentWillReceiveProps • shouldComponentUpdate • componentWillUpdate • componentDidUpdate • componentWillUnmount
Django Rest Framework • The Web browsable API is a huge usability win for your developers. • Authentication policies including packages for OAuth1a and OAuth2. • Serialization that supports both ORM and non-ORM data sources. • Customizable all the way down - just use regular function-based views if you don't need the more powerful features. • Extensive documentation, and great community support. • Used and trusted by large companies such as Mozilla and Eventbrite.
Model->Serializer->ViewSet class Todo(models.Model): text = models.CharField(max_length=300) marked = models.BooleanField(default=False) class TodoSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Todo fields = ('id', 'text', 'marked') class TodoViewSet(viewsets.ModelViewSet): queryset = Todo.objects.all() serializer_class = TodoSerializer
Demo
Three Principles of Redux • Single source of truth • State is read-only • Changes are made with pure functions
Redux State Tree / Store { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }
Reducers { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }
import * as types from '../constants/ActionTypes'; const initialState = []; export default function todos(state=initialState, action) { switch (action.type) { case types.ADD_TODO: return [...state, action.todo]; case types.DELETE_TODO: return state.filter(todo => todo.id !== action.id ); case types.EDIT_TODO: return state.map(todo => todo.id === action.todo.id ? action.todo : todo ); default: return state; } }
store.dispatch({ type: 'ADD_TODO', todo: { text: "Check how much time is left", marked: false } }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
Presentational Components • Are concerned with how things look. • Use props for displaying everything • Do not manage state at all • Don’t emit actions, but may take callbacks that do via props <MyComponent title=“No state, just props.” barLabels={["MD", "VA", "DE", "DC"]} barValues={[13.626332, 47.989636, 9.596008, 28.788024]} />
Container Component • Are concerned with how things work. • Responsible for providing data to presentational components via props • Also responsible for handling state changes triggered inside a presentation component via callback prop. These state changes are often done via dispatching an action.
class TodoApp extends Component { componentDidMount() { this.props.actions.getTodos(); } render() { const { todos, actions } = this.props; return ( <div> <Header addTodo={actions.addTodo} /> <MainSection todos={todos} actions={actions} /> </div> ); } } function mapState(state) { return { todos: state.todos }; } function mapDispatch(dispatch) { return { actions: bindActionCreators(TodoActions, dispatch) }; } export default connect(mapState, mapDispatch)(TodoApp);
Wiring Redux to DRF • Python package “django-js-reverse" for getting your url routes in your javascript • NPM package “redux-promise”
import * as types from '../constants/ActionTypes'; function deleteTodo(id) { return fetch(Urls.todo_detail(id), { method: 'delete', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, }).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
import * as types from '../constants/ActionTypes'; import * as api from ‘../path/to/MyApiLibrary'; function deleteTodo(id) { return api.deleteTodo(id).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
DEMO
Quick Recap i.e. TL;DR
You need a build tool to create bundles. Webpack is nice for this.
{% extends "base.html" %} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "bundle.css" %}"/> <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <script src="{% static "bundle.js" %}"></script> <!-- more content in Django template --> {% endblock %}
// index.js import React from 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') );
// TodoHeader.jsx import React, { Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
class Todo(models.Model): text = models.CharField(max_length=300) marked = models.BooleanField(default=False) class TodoSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Todo fields = ('id', 'text', 'marked') class TodoViewSet(viewsets.ModelViewSet): queryset = Todo.objects.all() serializer_class = TodoSerializer
import * as types from '../constants/ActionTypes'; const initialState = []; export default function todos(state=initialState, action) { switch (action.type) { case types.ADD_TODO: return [...state, action.todo]; case types.DELETE_TODO: return state.filter(todo => todo.id !== action.id ); case types.EDIT_TODO: return state.map(todo => todo.id === action.todo.id ? action.todo : todo ); default: return state; } }
import * as types from '../constants/ActionTypes'; function deleteTodo(id) { return fetch(Urls.todo_detail(id), { method: 'delete', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, }).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
Thanks! Eric Palakovich Carr @bigsassy on twitter and github example repo at https://github.com/bigsassy/drf-react-redux

Django Rest Framework and React and Redux, Oh My!

  • 1.
    Django Rest Framework andReact and Redux, Oh My!
  • 2.
    I’m Eric PalakovichCarr. Co-Founder & Chief Architect at Previously:
  • 3.
    What this talkis not • A comprehensive tutorial • A deep dive into how these technologies works • You WILL need to go and learn this stuff on your own after this talk
  • 8.
    {% extends "base.html"%} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <!-- more content in Django template --> {% endblock %}
  • 10.
    Javascript Ecosystem • We’llbe using NPM to manage your packages • We’ll be using webpack to handle bundling our assets. • We’ll be using scotch to burn away the memories of configuring the build system for our project.
  • 11.
    in a nutshell •The pip & cheeseshop / pypi of javascript • packages.json works like requirements.txt & setup.py • `npm init` in directory with packages.json generates node_modules. Kinda like a virtualenv directory. • packages.json also can act like your `manage.py` for your javascript code, but you populate it with custom commands.
  • 12.
    Building for Javascript •Start coding your project, using `npm install some-package — save` as you go. This creates and maintains your package.json. • Setup the config file for your build tool (webpack.config.js, gulpfile.js, Gruntfile, etc) • Add configs for Babel, minification, and other JS stuff • Add configs LESS, SASS, and other CSS stuff • Plus source mapping, tests, linters, sprite sheets, etc • Run your tool to generate bundles, having them save into your static directory for Django to pick up.
  • 13.
    {% extends "base.html"%} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "bundle.css" %}"/> <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <script src="{% static "bundle.js" %}"></script> <!-- more content in Django template --> {% endblock %}
  • 14.
    {% extends "base.html"%} {% load staticfiles %} {% load render_bundle from webpack_loader %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> {% render_bundle 'main' 'js' %} <!-- more content in Django template --> {% endblock %}
  • 15.
  • 17.
    // index.js import Reactfrom 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') );
  • 19.
    // index.js import Reactfrom 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') ); JSX
  • 20.
    // TodoHeader.jsx import React,{ Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
  • 21.
    JSX // TodoHeader.jsx import React,{ Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
  • 22.
    // TodoHeader.jsx import React,{ Component } from 'react'; export default class TodoHeader extends Component { render() { return React.createElement( "header", {className: "todoHeadCmp"}, React.createElement( "h1", null, this.props.listName, ) ); } }
  • 23.
    export default classTodoTextInput extends Component { constructor(props, context) { super(props, context); this.state = { text: this.props.text || '' }; } handleSubmit(e) { const text = e.target.value.trim(); if (e.which === 13) { this.props.onSave(text); } } handleChange(e) { this.setState({ text: e.target.value }); } render() { return ( <input type='text' value={this.state.text} onChange={::this.handleChange} onKeyDown={::this.handleSubmit} /> ); } }
  • 24.
    React Component Lifecycle •componentWillMount • componentDidMount • componentWillReceiveProps • shouldComponentUpdate • componentWillUpdate • componentDidUpdate • componentWillUnmount
  • 25.
    Django Rest Framework •The Web browsable API is a huge usability win for your developers. • Authentication policies including packages for OAuth1a and OAuth2. • Serialization that supports both ORM and non-ORM data sources. • Customizable all the way down - just use regular function-based views if you don't need the more powerful features. • Extensive documentation, and great community support. • Used and trusted by large companies such as Mozilla and Eventbrite.
  • 26.
    Model->Serializer->ViewSet class Todo(models.Model): text =models.CharField(max_length=300) marked = models.BooleanField(default=False) class TodoSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Todo fields = ('id', 'text', 'marked') class TodoViewSet(viewsets.ModelViewSet): queryset = Todo.objects.all() serializer_class = TodoSerializer
  • 27.
  • 28.
    Three Principles ofRedux • Single source of truth • State is read-only • Changes are made with pure functions
  • 29.
    Redux State Tree/ Store { visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }
  • 30.
    Reducers { visibilityFilter: 'SHOW_ALL', todos: [ { text:'Consider using Redux', completed: true, }, { text: 'Keep all state in a single tree', completed: false } ] }
  • 31.
    import * astypes from '../constants/ActionTypes'; const initialState = []; export default function todos(state=initialState, action) { switch (action.type) { case types.ADD_TODO: return [...state, action.todo]; case types.DELETE_TODO: return state.filter(todo => todo.id !== action.id ); case types.EDIT_TODO: return state.map(todo => todo.id === action.todo.id ? action.todo : todo ); default: return state; } }
  • 33.
    store.dispatch({ type: 'ADD_TODO', todo: { text:"Check how much time is left", marked: false } }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
  • 34.
    Presentational Components • Areconcerned with how things look. • Use props for displaying everything • Do not manage state at all • Don’t emit actions, but may take callbacks that do via props <MyComponent title=“No state, just props.” barLabels={["MD", "VA", "DE", "DC"]} barValues={[13.626332, 47.989636, 9.596008, 28.788024]} />
  • 35.
    Container Component • Areconcerned with how things work. • Responsible for providing data to presentational components via props • Also responsible for handling state changes triggered inside a presentation component via callback prop. These state changes are often done via dispatching an action.
  • 36.
    class TodoApp extendsComponent { componentDidMount() { this.props.actions.getTodos(); } render() { const { todos, actions } = this.props; return ( <div> <Header addTodo={actions.addTodo} /> <MainSection todos={todos} actions={actions} /> </div> ); } } function mapState(state) { return { todos: state.todos }; } function mapDispatch(dispatch) { return { actions: bindActionCreators(TodoActions, dispatch) }; } export default connect(mapState, mapDispatch)(TodoApp);
  • 37.
    Wiring Redux toDRF • Python package “django-js-reverse" for getting your url routes in your javascript • NPM package “redux-promise”
  • 38.
    import * astypes from '../constants/ActionTypes'; function deleteTodo(id) { return fetch(Urls.todo_detail(id), { method: 'delete', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, }).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
  • 39.
    import * astypes from '../constants/ActionTypes'; import * as api from ‘../path/to/MyApiLibrary'; function deleteTodo(id) { return api.deleteTodo(id).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
  • 40.
  • 41.
  • 42.
    You need abuild tool to create bundles. Webpack is nice for this.
  • 43.
    {% extends "base.html"%} {% load staticfiles %} {% block title %}Some Awesome Django Website{% endblock %} {% block extrahead %} <link rel="stylesheet" href="{% static "bundle.css" %}"/> <link rel="stylesheet" href="{% static "some.css" %}"/> <link rel="stylesheet" href="{% static "even_more.css" %}"/> {% endblock %} {% block body %} <!-- some content in Django template --> <div id="todolist"></div> <script src="{% static "bundle.js" %}"></script> <!-- more content in Django template --> {% endblock %}
  • 44.
    // index.js import Reactfrom 'react'; import ReactDOM from 'react-dom'; import TodoHeader from './TodoHeader.jsx'; ReactDOM.render( ( <div className="todoWidget"> <TodoHeader listName="todos" /> </div> ), document.getElementById('todolist') );
  • 45.
    // TodoHeader.jsx import React,{ Component } from 'react'; export default class TodoHeader extends Component { render() { return ( <header className='todoHeadCmp'> <h1>{this.props.listName}</h1> </header> ); } }
  • 46.
    class Todo(models.Model): text =models.CharField(max_length=300) marked = models.BooleanField(default=False) class TodoSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Todo fields = ('id', 'text', 'marked') class TodoViewSet(viewsets.ModelViewSet): queryset = Todo.objects.all() serializer_class = TodoSerializer
  • 47.
    import * astypes from '../constants/ActionTypes'; const initialState = []; export default function todos(state=initialState, action) { switch (action.type) { case types.ADD_TODO: return [...state, action.todo]; case types.DELETE_TODO: return state.filter(todo => todo.id !== action.id ); case types.EDIT_TODO: return state.map(todo => todo.id === action.todo.id ? action.todo : todo ); default: return state; } }
  • 48.
    import * astypes from '../constants/ActionTypes'; function deleteTodo(id) { return fetch(Urls.todo_detail(id), { method: 'delete', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, }).then(() => ({ type: types.DELETE_TODO, id: id })); } // In a component somewhere else store.dispatch(deleteTodo(this.props.todo.id))
  • 49.
    Thanks! Eric Palakovich Carr @bigsassyon twitter and github example repo at https://github.com/bigsassy/drf-react-redux