by Nicholas Boll
// components/article.js const Heading = ({ text }) => <h1>{text}</h1>; const Content = ({ text }) => <p>{text}</p>; const Article = ({ heading, content }) => ( <article> <Heading text={heading} /> <Content text={content} /> </article> ); export default Article;
// containers/article.js import { connect } from 'react-redux'; import Article from '../components/Article'; export default connect(state => state)(Article);
MorphDOM proves it isn't
What happens if the data we're passing isn't "Smart"? Or what if we need to touch something small on many similar components?
We need to load the right data at the right level
import {observer} from 'mobx-react'; const TodoView = observer(({todo}) => <div>{todo.title}</div>)
import * as React from 'react' import { isObservable } from 'mobx' import { observer } from 'mobx-react' export const mx = (Object.keys(React.DOM) .reduce((tags, tag) => { tags[tag] = createWrapper(tag); return answer; }, {}): any) function createWrapper(tag) { class ReactiveClass extends React.Component { static displayName = `mx.${tag}`; propKeys: string[]; constructor(props){ super(props); this.propKeys = Object.keys(props); } render() { const propValues = this.propKeys .reduce((answer, key) => { const value = this.props[key]; if(isObservableArray(value)){ answer[key] = value.peek(); } else if(isObservable(value) && value.get){ answer[key] = value.get(); } else { answer[key] = value; } return answer; }, {}); return React.createElement(tag, propValues); } } return observer(ReactiveClass); }
import * as React from 'react' import { mx } from './utils/mxReact' const Heading = ({ text }) => <mx.h1>{text}</mx.h1>; const Content = ({ text }) => <mx.p>{text}</mx.p>; const Article = ({ heading, content }) => ( <mx.article> <Heading text={heading} /> <Content text={content} /> </mx.article> ); export default Article;
// Take 1 or more observables, combine to to 1 result const computedProperty = (...observables) => Rx.Observable .combineLatest(...observables) // combine the last value of each observable .distinctUntilChanged() // only emit a value if it is different from previous .cache(1) // cache the last result if anyone asks...
const a$ = Rx.Observable.of(1) const b$ = Rx.Observable.of(2) const c$ = computedProperty( a$, b$, (a, b) => a + b ) c$.subscribe((c) => console.log(c)) // 3
// get the last value to flow through a computed property // synchronous resolution of a value const getValue = (computedProperty) => { let value rxValue.subscribe((x:T) => value = x).unsubscribe() return (value: any) }
const a$ = Rx.Observable.of(1) const b$ = Rx.Observable.of(2) const c$ = computedProperty( a$, b$, (a, b) => a + b ) console.log(getValue(c$)) // 3
const list = ({ scrollTop$, onScroll }) => { // This only gets defined once because properties are immutable pipelines // that don't change references from one render to the next. No breaking // pure-render const onWheel = (event) => ( onScroll(getValue(scrollTop$) + event.wheelDeltaY) ) const style$ = computeProperty( scrollTop$, (scrollTop) => { return { transform: `transform: translate3d(0px, ${scrollTop}px, 0px);` } } ) return ( <rx.div class="scrollable-container" onWheel={onWheel} style={style$} > { /* reactive children */ } </rx.div> ) }
Note: What I didn't show was the reactive children - it is a bit complicated to get into now, children are a computedProperty of observer components. Ex: Rx.Observable.of([ ListItem1, ListItem2 ]). We have a helper that takes a projection component (Ex: ListItem) and an array of indexes and returns an observable of an array of projected components. Similar to { items.map((item, index) => <ListItem key={index} data={item} />) }