KEMBAR78
Django Rest Framework and React and Redux, Oh My! | PDF
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