React Basics
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):
- componentWillMount
- render
- componentDidMount
- 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'
}