Day 1, Segment 5
Branch v2-10
Shape in Proptypes
You can use
shape()
:
const { shape, string, bool } = React.PropTypes
const ShowCard = React.createClass({
propTypes: {
show: shape({
poster: string,
title: string,
year: string,
description: string
})
},
render () {
const { poster, title, year, description } = this.props
return (...)
}
})
You describe the object you're going to use. If you know what your object is going to look like, then it's a good idea to use shape to describe it. Similar to array of
.You can nest shapes in shape.
Shorten the JSON data declaration with the spread operator
Use the spread the operator (
...
) in JSX. This is JSX, but mimics ES6.
...
import preload from '../public/data.json'
const Search = React.createClass({
render () {
return (
<div className='search'>
{preload.shows.map((show) => {
return (
// 1. ++++
<ShowCard key={show.imdbID} {...show} />
)
})}
</div>
)
}
})
...
The above is same as
<ShowCard key={show.imdbID} {...show} />
// same as
<ShowCard key={show.imdbID} poster={show.poster} title={show.title} />
Usually explicit is better, but sometimes can be useful. The spread will arrive in ES2017.Update component proptype after the spread
Now, with spread operation above, the child component ShowCard receives not one object proptype, but as a list. Let's update this component:
// **** ShowCard.js ****
import React from 'react'
// 3. ++++
const { string } = React.PropTypes
const ShowCard = React.createClass({
// 2. ++-- can remove shape
propTypes: {
poster: string,
title: string,
year: string,
description: string
},
render () {
// 1. ++-- this will be just this.props
// and not this.props.show
// const { poster, title, year, description } = this.props.show
const { poster, title, year, description } = this.props
return (
<div className='show-card'>
<img src={`/public/img/posters/${poster}`} />
<div>
<h3>{title}</h3>
<h4>({year})</h4>
<p>{description}</p>
</div>
</div>
)
}
})
export default ShowCard
This is a better, cleaner pattern as opposed to passing the .show
object. Less nesting is almost always better when it comes to proptypes. It's simple to know what proptypes you need to pass to the component.Setting default values to proptype
getDefaultProps
. But if you're using it too often, you're probably doing it wrong. Later, state
will explain why...React state
One way data-flow
"State is the enemy of all apps"
If you have a bug it's always coming from the state.
React tries to minimise this problem, by reducing the number of places you can modify state. This resonates with the idea that if you find a bug in your app, you know the one place, where to look (to debug it).
There's no sideways modification of state in React. If you modify state, it's in the component itself, nowhere else.
Branch v2-10
(insider joke about Henriks yolo in his FEM workshop...)
09:00
For example: Search component has a child ShowCard. ShowCard cannot modify Search's state. So if you have a bug, it can only come from Search itself.
Notice that the sub-component doesn't, and cannot pass anything (proptypes, etc) up, to the parent component. This is called one-way data flow. React pioneered it, now Ember does it too. Data always flows down.
How do you move data up then?
11:20
You take the data out from the sub-component and put it in the parent component and pass it down again. If two components share the same data, then the common ancestor is where that data lives.
Will show a concrete example later.
What if your component has a button, and it has to affect a parent element?
Notify the parent with a callback function
We pass a (callback) function to the child that it can modify when its state changes and then the parent component will modify itself its own state.
Make React keep track of state
The state will be the search term.
// **** Search.js
import React from 'react'
import ShowCard from './ShowCard'
import preload from '../public/data.json'
const Search = React.createClass({
// 2.++++ store the state here
// (ES6 enhanced object literal syntax)
getInitialState () {
return {
searchTerm: ''
}
},
// 3. ++++ React synthetic event
handleSearchTermChange (event) {
this.setState({searchTerm: event.target.value})
},
render () {
return (
<div className='search'>
<header>
// 1.++++
<h1>svideo</h1>
// value={this.state.searchTerm}
// the input field value will auto-update
<input onChange={this.handleSearchTermChange} value={this.state.searchTerm} type='text' placeholder='Search' />
</header>
<div>
{preload.shows
// 4. ++++ filter (explained much later below...)
.filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0)
.map((show) => {
return (
<ShowCard key={show.imdbID} {...show} />
)
})}
</div>
</div>
)
}
})
export default Search
By design, there's no two-way data binding in React. So above, <input value={this.state.searchTerm}
will not auto update yet because nothing modifies yet this.state.searchTerm
. React does listen to and captures all keyboard events. (Debugging two-way databinding (Angular) is very hard, too much magic there...)
So we need a function to update
this.state.searchTerm
when typing: onChange={this.handleSearchTermChange}
React synthetic events
React uses synthetic events, that is it captures the events first and you use the synthetic event passed to you by React.
event.target.value
is what you would get with the original event.
handleSearchTermChange (event) {
this.setState({searchTerm: event.target.value})
},
setState
This is the only way in React to modify the state. You only modify the delta ie. the only part of the data you're changing. Behind the scenes, React will do an
Object.assign
to merge it into complete state data. It doesn't do a deep merge, so if you have a deeply nested data, you have to handle it yourself.
And now, as the state has been updated, it will kick off a re-render.
forceUpdate
If you modify the state not using
setState
, then it will not do a re-render. In this case you need to do this.forceUpdate()
.This is not efficient.
With
setState
, React will batch the rerenders. It's an async function and will queue the update. So, never use
forceUpdate
!Except, if you need to integrate it with D3 (or another library), which has to be in control.
VirtualDOM
It's just an implementation detail in React. "It does not make React desirable, only feasible".
What matters is that it works and it's fast. You don't need to know how it works (for debugging).
State over time (because of spaghetti code)
State over time is what makes app difficult to debug. This is a particularity of spaghetti code, you end up writing with jQuery. State builds up over time, as you add more and more event handlers.
React eliminates the time component
Given a state, which can be thought of as a snapshot, the component always behaves the same way.
Filtering the list (search)
From above...
Use JavaScript
Array.filter
and String.indexOf
for string matching. And String.toUpperCase
to ignore case. Not very smart, but OK for the demo...
{preload.shows
// 4. ++++ filter (explained much later below...)
.filter((show) => `${show.title} ${show.description}`.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0)
.map((show) => {
return (
<ShowCard key={show.imdbID} {...show} />
)
})}