django rest framework and react and redux, oh my!
TRANSCRIPT
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->ViewSetclass 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 elsestore.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 elsestore.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 elsestore.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