React + Redux + d3 Teemu Kurppa, ŌURA
React + Redux + d3Teemu Kurppa, ŌURA
www.ouraring.com
the world's first wellness ring
Head of Cloud
I work at
• Declarative UI Library
• Battle-tested by Facebook
• Component-based
• Inline HTML with JSX
• Predictable state management for JS Apps
• Great tooling: logging, hot loading, time travel,…
d3.js
d3 - a lot of good stuff
• Handling data: Scales, Interpolators, Time Intervals, Time & Number formats
• Drawing: Paths, Areas, Shapes, Curves, Axes
• Data Structures for Visualization: Voronoi, Stack, Quadtrees, Polygons
• Drawing maps: Projections, Spherical Math, Geopaths
• Animations and Behaviour: Transitions, Easing, Zooming, Dragging, Forces
d3 - problems
• No components: code gets unwieldy very quickly
• State management mixed with UI code
d3 class pattern
class D3SeriesBarChart { constructor(el, props, state) { // setup code } update(el, props, state) { // data-dependent code } }
React wrapper for d3class SeriesBarChart extends Component { componentDidMount() { this.chart = new D3SeriesBarChart(this.svg, this.props, this.state); }
componentDidUpdate() { this.chart.update(this.svg, this.props, this.state); }
render() { return ( <svg ref={svg => this.svg = svg} width={this.props.width} height={this.props.height}> </svg> ); } }
React wrapper for d3class SeriesBarChart extends Component { componentDidMount() { this.chart = new D3SeriesBarChart(this.svg, this.props, this.state); }
componentDidUpdate() { this.chart.update(this.svg, this.props, this.state); }
render() { return ( <svg ref={svg => this.svg = svg} width={this.props.width} height={this.props.height}> </svg> ); } }
React wrapper for d3var T = React.PropTypes;
SeriesBarChart.propTypes = { width: T.number.isRequired, height: T.number.isRequired, data: T.shape({ x: T.arrayOf(T.number), y: T.arrayOf(T.arrayOf(T.number)), }).isRequired, mode: T.string.isRequired }
Demo Componentclass Demo extends Component { render() { return ( <div className={classNames(styles.demo)}> <form> {this.makeRadio('grouped', this.props.onSelectGrouped)} {this.makeRadio('stacked', this.props.onSelectStacked)} </form> <SeriesBarChart width={960} height={500}
data={this.props.data} mode={this.props.mode}/>
<div> <button onClick={this.props.onGenerateData}> Generate Data </button> </div> </div> ); } // … }
Demo Component
class Demo extends Component { // … makeRadio(mode, onChange) { return ( <label><input type=“radio" name=“mode" value={mode} onChange={onChange} checked={this.props.mode === mode}/> {mode} </label> ) } }
Redu Reducer// reducers.js
const initialState = { data: createData(), mode: 'grouped' };
function demo(state = initialState, action) { switch (action.type) { case CHANGE_MODE: return { ...state, mode: action.mode }; case GENERATE_DATA: return { ...state, data: createData() }; default: return state; } }
const rootReducer = combineReducers({ demo, });
export default rootReducer;
Component CSS// SeriesBarChart.jsx
import classNames from 'classnames'; const styles = require('./SeriesBarChart.scss');
class SeriesBarChart extends Component { render() {
return ( <svg className={classNames(styles.seriesbarchart)} ref={svg => this.svg = svg} width={this.props.width} height={this.props.height}> </svg> ); } }
Component CSS// SeriesBarChart.scss
.seriesbarchart { :global { .axis text { fill: blue; } } }
Thanks! Questions?
We are hiring: Python backend developers web front-end developers
Follow @teemu on Twitter to stay in touch.
Email: [email protected]