Writing modern SPA applications

part 3: REACT.JS

different approaches for

rendering ui

Render once, then bind events and modify dom

rerender entire component on state change

virtual dom

virtual dom

virtual dom

  • faster than always updating and then rerendering and repainting DOM (which is slow)
     
  • does not require rebinding of events (which is slow)
     
  • optimizied with simple heuristic to make tree comparison as fast as possible
     
  • make UI changes predictable and trackable

component based

application design

traditional mvc architecture

component-based architecture

what exactly is this

component

component structure

component lifecycle

wait a minute

how children work

JSX UNDER THE HOOD

const foo = (
  <div className="button">
    <button type="submit"/> 
  </div>
)

// this gets transpiled to

const foo = (
  React.createElement("div", {
    className: "button"
    children: [
      React.createElement("button", { type: "submit" })
    ]
  })
)

components can be

objects

react.component

import React, { Component } from 'react'
import { Link } from 'react-router-dom'

class Dropdown extends Component {
  onToggle() {
    this.setState({ visible: !this.state.visible })
  }

  render() {
    return (
      <div className="dropdown">
        <button className="dropdown__button" onClick={this.onToggle}>
          Menu
        </button>
        {this.state.visible && 
        <div className="dropdown__content">
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
          <Link to="/pricing">Pricing</Link>
        </div>}
      </div>
    )
  }
}

components can be

functions

stateless component

import React from 'react'

const Button = function Button({ type, ...props }) {
  const className = type === 'white' ? 'button__white' : 'button__black'
  return (
    <button className={className} {...props}/>
  )
}


const Input = observer(function Input({ field }) {
  return (
    <div className={classNames("form-group", {"has-error": field.error})}>
      {field.error && 
      <span className="form-group__error">{field.error}</span>}

      <input className="form-control" {...field.bind()}/>
    </div>
  )
})

components can be

composed

higher order component

const StyledInput = function StyledInput(props) {
  return (
    <Input style={{ color: 'red' }} {...props}/>
  )
}

// example use:

<StyledInput name="foo"/>
<StyledInput name="bar"/>

higher order component

class UserFetch extends React.Component {
  componentWillMount() {
    fetch('/api/users').then((users) => this.setState({ users: users }))
  }

  render() {
    const WrappedComponent = React.Children.only(this.props.children)

    if(typeof this.state.users === 'undefined') {
      return <div className="loading-spinner"/>
    } else {
      return React.cloneElement(WrappedComponent, { users: this.state.users })
    }
  }
}

// example:
const UserCount = ({ users }) => <div>User count: {users.length}</div>
const Users = () => (
  <div>
    <UserFetch><MyComponent/></UserFetch>
  </div>
)

higher order component

function withUserFetching(WrappedComponent) {
  return class extends React.Component {
    componentWillMount() {
      fetch('/api/users').then((users) => this.setState({ users: users }))
    }

    render() {
      if(typeof this.state.users === 'undefined') {
        return <div className="loading-spinner"/>
      } else {
        return <WrappedComponent users={this.state.users}/>
      }
    }
  }
}

// example:
const FetchingUserCount = withUserFetching(UserCount)
const Users = () => (
  <div>
    <FetchingUserCount/>
  </div>
)

HOC RULES:

  • Compose instead of using inheritance to get behaviours you need
     
  • Don't modify the original class, wrap it instead
     
  • Pass unrelated props to underlying component when possible ({...props})
     
  • Avoid using fat arrow syntax and use named functions instead, they show up in DevTools making them easier to spot and debug
     
  • Do not apply HOC-making functions in render() method

HOC CAVEATS

  • Since we are composing by wrapping, static methods are not passed over and refs refer to the wrapping component, not the one underneath
     
  • Statics can be copied manually or with hoist-non-react-statics
     
  • To access proper refs you can pass ref callback via custom prop
const Input = function Input({ inputRef, ...props }) {
  return <input ref={inputRef} {...props}/>
}

handling and passing

state and props

PASSING STATE DOWN WITH PROPS

pushing state up with callbacks

state management with

mobx

what is mobx

how does it work

mobx

  • Everything in MobX works around observables. They are properties of objects for which access can be tracked.
     
  • Tracking of access happens inside function passed to autorun (or reaction). The function is then re-run every time observable value changes.
     
  • Actions are functions wrapped in transaction - reactions inside them are batched together an run after the function finishes.
     
  • Computed properties are cached properties that get updated when observables used to calculate them change

mobx example

import { observable, autorun } from 'mobx'
class Store {
  @observable foo = 1
}

const store = new Store
autorun(() => console.log(store.foo))

store.foo = 2 // causes console.log to be called and 2 to be printed
store.foo = 3 // ditto, 3 is printed

mobx-react

  • MobX can be connected to React via observer() function
     
  • This function wraps our component and makes it reactive.
     
  • Reactive component is a component that rerenders automatically if any observable used to render it changes.
     
  • @observable can replace this.state and setState completely

mobx-react

import React from 'react'
import { observable } from 'mobx'
import { observer } from 'mobx-react'

class LikeButton extends React.Component {
  @observable liked = false

  render() {
    return (
      <div>
        {this.liked ? "I like this" : "I don't like this"}
        <button onClick={() => this.liked = !this.liked}/>
      </div>
    )
  }
}

export default observer(LikeButton)

data can be kept

inside stores

stores

import { observable, action, computed } from 'mobx'

class UserStore {
  @observable users = []
  @observable current = null

  fetch() {
    fetch("/api/users").then(this.setUsers)
  }

  @action setUsers(users) {
    this.users = users
  }

  @computed get currentUser() {
    if(this.current !== null) {
      return this.users.get(this.current)
    }
  }
}

stores

  • Stores are source of data inside the application
     
  • They can use Models, Repositories and other OO patterns
     
  • Stores should be created per domain (users, projects etc)
     
  • If relation between two entities is containment  (one belongs to other), it makes sense to put them in one store
     
  • There is always more than one way of doing things and there are many libraries that solve the problem of data storage

passing stores

using provider/inject

Provider

const userStore = new UserStore
const projectStore = new ProjectStore
const stores = { userStore, projectStore }

const App = function App() {
  return (
    <Provider {..stores}>
      <Header/>
      <Routing/>
      <Footer/>
    </Provider>
  )
}

inject using functions

const Header = function Header({ userStore }) {
  return (
    <header>
      {userStore.currentUser 
        ? <div>You are not logged in</div>
        : <div>You are logged in as {userStore.currentUser.name}
      }
    </header>
  )
}

export default inject('userStore')(observer(Header))

inject using decorators

@inject('userStore')
@observer
class Footer extends React.Component {
  render() {
    const { userStore } = this.props
    return (
      <div>There are {userStore.users.length} users loaded</div>
    )
  }
}

time for

summary

react

  • React is a component-based library, not "just a View in MVC"
     
  • React components are re-rendered when their props or state change
     
  • Components can be nested in each other and create a tree structure
     
  • Applications written in React are using composing (wrapping) instead of OO inheritance to share common functionality
     
  • Virtual DOM is used to make sure the amount of perf-heavy operations (event binding, DOM manipulation) is minimal

mobx

  • MobX is a library for making things reactive
     
  • Core features: observable, action, computed, autorun
     
  • MobX can be used to make React components reactive with observer(), stores can be passed with Provider/inject
     
  • Common pattern of handling data involves creating Stores and keeping data there so they can be shared between multiple unrelated components inside application

that's it

questions?

Made with Slides.com