Posted on 8 mins read

Edit 14/6/17: Fix formatting.

ReactJS to learn React Native. Essentially, trying to follow the learning path here: link

Obviously, certain parts will be prioritised.

Skeleton

App.js

import React from 'react';

// Component with Stateless
class App extends React.Component {
    render() {
        return <h1>Hello Guys</h1>
    }
}

// Stateless component
// const App = () => <h1>Hello Eggheads</h1>

export default App;

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Intro</title>
</head>
<body>
    <div id="app"></div>
    <script src="index.js"></script>
</body>
</html>

main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('app'));

Components

Base

import { Component } from 'react'

class Thing extends Component {
  render() {
    return <h1>Hello World!</h1>
  }
}

Stateless function

const Thing = () => <h1>Hello Darth Vader!</h1>

Loading children

const Thing = (props) => <h1>Hello Darth Vader! {props.children}</h1>

Owner, Ownee

Entry in input updates h1.

class Thing extends Component {
  constructor() {
    super()
    this.state = {
      stuff: "this is the state stuff"
    }
  }

  update(e) {
    //update with input value
    this.setState({stuff: e.target.value})
  }

  render() {
    return (
        <div>
          <h1>{this.state.stuff}</h1>
          <input type="text" onChange={this.update.bind(this)} />
        </div>
      )
  }
}

export default Thing

Multiple widgets access parent update method. Notice the update prop for Widget in the render method of Thing.

class Thing extends Component {
  constructor() {
    super()
    this.state = {
      stuff: "this is the state stuff"
    }
  }

  update(e) {
    //update with input value
    this.setState({stuff: e.target.value})
  }

  render() {
    return (
        <div>
          <h1>{this.state.stuff}</h1>
          <Widget type="text" update={this.update.bind(this)} />
          <Widget type="text" update={this.update.bind(this)} />
          <Widget type="text" update={this.update.bind(this)} />
        </div>
      )
  }
}

const Widget = (props) =>
<input type="text" onChange={props.update} />

export default Thing

Validation

Default validation propTypes

const Thing extends Component {

  render() {
    let stuff = this.props.stuff
    return (
        <h1>{stuff}</h1>
      )
  }
}

Thing.propTypes = {
  stuff: React.PropTypes.string,
  cat: React.PropTypes.number.isRequired
}

Thing.defaultProps = {
  stuff: "this is the default props"
}

export default Thing


//App.js render
<Thing cat={42} />

Custom Validation

Instead of returning react propType, return a custom function.

class Thing extends Component {

  render() {
    return (
        <Title info="important info!"/>
      )
  }
}

const Title = (props) => <h1>Title: {props.info}</h1>

Title.propTypes = {
  text(props, propName, component) {
    if(!(propName in props)) {
      //ensure prop exists
      // `` means evaluate variable in string
      return new Error(`missing ${propName}`)
    }
    if(props[propName].length < 6) {
      return new Error(`${propName} is too short.`)
    }
  }
}


export default Thing

Refs or References

Refs are a way for us to reference a node or an instance of a component. Ref simply returns the node we are referencing.

class App extends React.Component {
  constructor() {
    super();
    this.state = { a: ''}
  }

  update() {
    this.setState({
      a: this.a.value,
      b: this.refs.labelForInput2.value
      })
  }

  render() {
    return(
      <div>
        <input
          ref={ node => this.a = node }
          type="text"
          onChange={this.update.bind(this)}
        />
        <p> {this.state.a} </p>
        <input
          ref="labelForInput2"
          type="text"
          onChange={this.update.bind(this)}
        />
        <p> {this.state.b} </p>        
      </div>
      )
    })
}

Reference an instance of another component

import ReactDOM from 'react-dom';

update() {
  this.setState({
    a: this.a.value,
    b: this.refs.labelForInput2.value,
    c: this.c.refs.inputRef.value
  })
}

/**
 * If there is only 1 element in render() for the other component.
 * c: ReactDOM.findDOMNode(this.c).value works.
 */


render() {
  return(
    <div>
      <input
        ref={ node => this.a = node }
        type="text"
        onChange={this.update.bind(this)}
      />
      <p> {this.state.a} </p>
      <input
        ref="labelForInput2"
        type="text"
        onChange={this.update.bind(this)}
      />
      <p> {this.state.b} </p>
      <input
        ref={ component => this.c = component }
        update={this.update.bind(this)}
      />
      <p> {this.state.c} </p>            
    </div>
    )
  })

class Input extends React.Component {
  render() {
    return (
        <div><input ref="inputRef" type="text" onChange={this.props.update}/></div>
      )
  }
}

Component Lifecycle

//Note: getInitialState deprecated in favor of declaring initial state value in constructor. Link.

export default class Loginform extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      name: '',
      password: ''
    };
  };
}

Example (rough order):

  1. componentWillMount
  2. render
  3. componentDidMount
  4. componentWillUnmount
class App extends React.Component {
  constructor() {
    super()
    this.state = {gryffindorPoints: 0}
    this.update = this.update.bind(this)
  }

  update() {
    this.setState({ gryffindorPoints: this.state.gryffindorPoints + 1})
  }

  componentWillMount() {
    //only fires once for component
    console.log('componentWillMount')

    //can set state before component is rendered
    this.setState({multiplier: 2})
  }

  render() {
    console.log('render')
    return <button onClick={this.update>{this.state.gryffindorPoints}</button>
  }

  componentDidMount() {
    //fires once component mounted to DOM
    console.log('componentDidMount')

    //multiply every 500ms
    this.increment = setInterval(this.update, 500)
  }  

   componentWillUnmount() {
     //Called when ReactDOM.unmountComponentAtNode(...) is fired.
     console.log('componentWillUnmount')

     //can use to clean update when unmounting
     clearInterval(this.increment)
   }
}

Component update lifecycle

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  constructor(){
    super();
    this.update = this.update.bind(this);
    this.state = {increasing: false}
  }
  update(){
    ReactDOM.render(
      <App val={this.props.val+1} />,
      document.getElementById('app')
    );
  }
  componentWillReceiveProps(nextProps){
    //new properties are coming in? then you have nextProps
    this.setState({increasing: nextProps.val > this.props.val})
  }
  shouldComponentUpdate(nextProps, nextState) {
    // does NOT prevent state or props from being updated.
    // only prevents re-render if not a multiple of 5.
    return nextProps.val % 5 === 0;
  }
  render(){
    console.log(this.state.increasing)
    return (
      <button onClick={this.update}>
        {this.props.val}
      </button>)
  }
  componentDidUpdate(prevProps, prevState) {
    // just as the arguments say.
    console.log('prevProps', prevProps)
  }
}

App.defaultProps = { val: 0 }

export default App

Dynamically generated components

import React from 'react';

class App extends React.Component {
  constructor() {
    super();
    this.state = {items: []}
  }

  componentWillMount() {
    fetch( 'http://swapi.co/api/people/?format=json')
      .then( response => response.json() )
      .then( ({result: items}) => this.setState({items}) )
  }

  filter(e) {
    //takes event of an input
    this.setState({filter: e.target.value})
  }

  render() {
    let items = this.state.items;
    if(this.state.filter) {
      items = items.filter( item =>
        item.name.toLowerCase()
        .includes(this.state.filter.toLowerCase()))
    }

    //"key" is amongst siblings. therefore the "key" tag is placed here.
    //In the context of the Person h4 component it has no siblings.
    return (
      <div>
        <input type="text" onChange={this.filter.bind(this)} />
        {items.map(item =>
          <Person key={item.name} person={item} />)}
      </div>
      )
  }
}

const Person = (props) => <h4> {props.person.name} </h4>

Higher order components

Purpose of higher component is to share functionality or information between multiple components.

Sole function is to take in a component and return a component.

const HOC = (InnerComponent) => class extends React.Component {
  constructor() {
    super();
    this.state = {count: 0}
  }

  update() {
    this.setState({count: this.state.count + 1})
  }

  componentWillMount() {
    console.log('HOC will mount')
  }


  render() {

    // "..." spread this.props into this component
    // pass the props right on through

    //here we are passing props, state and update function to the component
    return(
      <InnerComponent
        {...this.props}
        {...this.state}
        update={this.update.bind(this)}
      />
      )
  }
}

//displays props and state passed to it.
//uses function passed to it for onClick event
const Button = HOC((props) => <button onClick={props.update}>{props.children} , {props.count}</button>)

class Label extends React.Component {
  componentWillMount() {
    console.log('Label will mount')
  }

  render() {
    return (
      <label onMouseMove={this.props.update}>
        {this.props.children} , {this.props.count}
      </label>
      )
  }
}

const LabelHOC = HOC(Label)

JSX Deep dive

JSX deep dive with babel transpiler to es2015.

Does not work because you are returning 2 functions.

const App = (prop) => {
  return (
    <App />
    <App />
    )
}

// with a single <App /> equivalent to
var App = function App(props) {
  return React.createElement(App, null);
}

Wrap in a div tag.


<div>
  <App />
  <App />
</div>

var App = function App(props) {
  return React.createElement(
    "div",
    null,
    React.createElement(App, null),
    React.createElement(App, null)
    )
}

With props and stuff.

var coolStyles = {
  backgroundColor: 'purple',
  color: 'white'
}

<div style={coolStyles}>
  <a href="#"
    notrendered="x" // include hyphen or use data-something convention for component to be rendered
    onClick={update} //interpolation requires curly braces
  </a>
</div>

React.createElement(
  "div",
  { style: coolStyles },
  React.createElement(
    "a",
    { href: "#",
    notrendered: "x",
    onClick: update
    })
  );

Children utilities

Might want iterate through children

class App extends React.Component {
  render() {
    return (
      <Parent>
        <div className="childA"></div>
        <div className="childB"></div>
      </Parent>
      )
  }
}

class Parent extends React.Component {
  render() {
    //line below fails as this.props.childen is just a single object.
    //let items = this.props.children.map(child => child)
    let items = React.Children
      .map(this.props.childen, child => child)
    let items = React.Children
      .forEach(this.props.childen, child => console.log(child.props.className))

    //let items = React.Children.toArray(this.props.children)

    //throws error because expecting only 1 child.
    //let items = React.Children.only(this.props.childen)

    console.log(items)
    return null
  }
}

Extend functionality of children

this.props.children is just a descriptor of the children. You can’t actually modify anything. So to modify you need to create new elements.

class Button extends React.Component {
  constructor() {
    super();
    this.state = {selected: 'None'}
  }

  selectItem(selected) {
    this.setState({selected})
  }

  render() {
    let fn = child =>
      React.cloneElement(child, {
          onClick: this.selectItem.bind(this, child.props.value)
        })

    let items = React.Children.map(this.props.children, fn);
    return (
      <div>
        <h2> You have Selected: {this.state.selected} </h2>
        {items}
      </div>
      )
  }
}

Reusable and Composable components

A key tenant of react.

Old slider component using refs. Not really reusable.

class App extends React.Component {
  constructor() {
    super();
    this.state =  {
      red: 0,
      green: 0,
      blue: 0
    }
    this.update = this.update.bind(this)
  }
  update(e) {
    this.setState({
      red: ReactDOM.findDomeNode(this.refs.red.refs.inp).value,
      green: ReactDOM.findDomeNode(this.refs.green.refs.inp).value,
      blue: ReactDOM.findDomeNode(this.refs.blue.refs.inp).value
      })
  }

  render() {
    return (
      <div>
        <Slider ref="red" update={this.update} />
        {this.state.red}
        <Slider ref="green" update={this.update} />
        {this.state.green}
        <Slider ref="blue" update={this.update} />
        {this.state.blue}                
      )
  }
}

class Slider extends React.Component {
  render() {
    return (
      <div>
      <input ref="inp" type="range"
        min="0"
        max="255"
        onChange={this.props.update} />
      </div>
      )
  }
}

Now we try to create a more reusable component taking advantage of the similar apis between number input and range input.

class App extends React.Component {
  constructor() {
    super();
    this.state =  {
      red: 0,
      green: 0,
      blue: 0
    }
    this.update = this.update.bind(this)
  }
  update(e) {
    this.setState({
      red: ReactDOM.findDomeNode(this.refs.red.refs.inp).value,
      })
  }

  render() {
    return (
      <div>
        <NumInput
          ref="red"
          type="range"
          min={0}
          max={this.props.max}
          step={1}
          val={+this.state.red}
          label="Red"
          update={this.update} />     
      )
  }
}

class NumInput extends React.Component {
  render() {
    let label = this.props.label !== '' ?
      <label>{this.props.label}, {this.props.val}</label>
    return (
      <div>
      <input ref="inp"
        type={this.props.type}
        min={this.props.min}
        max={this.props.max}
        step={this.props.step}
        defaultValue={this.props.val}
        onChange={this.props.update} />
        {label}
      </div>
      )
  }
}

// Declare types (feels like input validation)
NumInput.propTypes = {
  min: React.PropTypes.number,
  max: React.PropTypes.number,
  step: React.PropTypes.number,
  val: React.PropTypes.number,
  label: React.PropTypes.string,
  update: React.PropTypes.func.isRequired, //update method type of func. is required.
  type: React.PropTypes.oneOf(['number', 'range']) // type array and only accepts number or range.
}

NumInput.defaultProps = {
  min: 0,
  max: 0,
  step: 1,
  val: 0,
  label: '',
  type: 'range'
}