LDS Stack Team
Run these commands
node --version # >= 8.x.x
npm --version # >= 5.2.x
ldsjs --version # >= 1.1.3
ldsjs react bootcamp-labs
cd bootcamp-labs
git init
git add -A
git commit -m "Initial commit"
npm install
npm run dev
then open localhost:3000 to view app
npm run storybook
then open localhost:9999 to view storybook
Part 1
Modern JavaScript
due to a lack of a module system in Javascript, two competing workarounds had been developed: CommonJS (node), and AMD (RequireJS).
The following is an example of a CommonJs module, which is commonly found in Node.js on the server, and Browserify on the client.
// lib.js (the export)
module.exports = {
lib: () => { ... }
};
// file.js (the import)
var { lib } = require('./lib.js');
An ES6 module is a file containing JS code. There’s no special module keyword; a module mostly reads just like a script. There are two differences.
ES6 modules are automatically strict-mode code, even if you don’t write "use strict"; in them.
You can use import and export in modules.
An ES6 module is a file containing JS code. There’s no special module keyword; a module mostly reads just like a script.
// lib.js (the exporter)
export default () => {}
export const mySpecificFunction = () => {}
export { objA, objB, funcC, funcD }
// file.js (the importer)
import lib from './lib.js';
import { mySpecificFunction } from './lib.js';
import lib, { mySpecificFunction } from './lib.js';
import lib, { mySpecificFunction, objA } from './lib.js';
import * as everything from './lib.js';
import lib, * as everything from './lib.js';
import { mySpecificFunction as msf } from './lib.js';
modules can both import and export
import dep from "./my-deps";
export default () => {}
export const mySpecificFunction = () => {}
Import statements must be first (at the top of a file)
Everything declared inside a module is local to the module, by default. If you want something declared in a module to be public, so that other modules can use it, you must export that feature.
// file.js
// only in this module's scope
const mySecretVariable = "hello";
export const mySpecificFunction = () => {}
Modern JavaScript
//square named function
function square(a) {
return a * a
}
//square function expression
const square = function(a) {
return a * a;
}
//function expression using arrow syntax
const square = (a) => {
return a * a;
};
//with Implicit Return
const square = (a) => a * a;
//with no parameter parenthesis
const square = a => a * a;
console.log(square(4)); //16
The functions to the left are all functionally equivalent. They accept a number, and return the square of that number.
param => true;
param => {return true};
(param1, param2) => (param1 + param2);
() => null;
() => { return true; }
() => (true)
() => ({ a: 1, b: 2 })
It will be helpful to be able to recognize different variations of function signatures by sight, and see that they all fall into the same pattern:
If you are new to this syntax, consider always wrapping the parameters in parenthesis, and using a function body with an explicit return. This verbose style will work everywhere
const square = (a) => {
return a * a;
}
[].map((elem) => {
return {
a: elem.a,
b: elem.deep.b
}
});
The recommendation is to use the appropriate syntax for the situation.
// scales verbosely
[].map((x, y) => {
return x * y;
});
// scales nicely
[].map( (x, y) => x * y);
lab
Similar to self in languages like Python and C#
this is usually the object a method is called on
this
this
In arrow functions, this retains the value of the enclosing lexical context's this. In global code, it will be set to the global object.
//Using Arrow Function
function Person(){
this.age = 0;
setInterval(() => {
// |this| refers to the current Person object
this.age++;
}, 1000);
}
var p = new Person();
//No Arrow (setting this to a variable)
function Person() {
var that = this;
that.age = 0;
setInterval(function growUp() {
// The callback refers to
// the `that` variable of which
// the value is the expected object.
that.age++;
}, 1000);
}
var p = new Person();
When a function is called as a method of an object, its this is set to the object the method is called on.
this
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
When a function is used as an event handler, its this is set to the element the event fired from
// When called as a listener,
// turns the related element blue
function bluify(e) {
// Always true
console.log(this === e.currentTarget);
// true when currentTarget and
// target are the same object
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// Get a list of every element in the document
var elements = document.getElementsByTagName('*');
// Add bluify as a click listener so when the
// element is clicked on, it turns blue
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', bluify, false);
}
this
// An object can be passed
// as the first argument
// to call or apply and
// this will be bound to it.
var obj = {a: 'Custom'};
// This property is set
// on the global object
var a = 'Global';
function whatsThis() {
return this.a;
}
whatsThis(); // 'Global'
whatsThis.call(obj); // 'Custom'
whatsThis.apply(obj); // 'Custom'
Modern JavaScript
items.map( x => x * x )
The map() method creates a new array with the results of calling a provided function on every element
The new array will be the same size as the original array
items.map( x => x * x )
Accepts a Function which...
optional arguments 2 and 3 are less commonly used
[0, 1, 2, 3, 4, 5, 6, 7]
.map((x) => 2**x)
// [1, 2, 4, 8, 16, 32, 64, 128]
[
{w:10, h:20, d:10},
{w:3, h:2, d:20},
{w:4, h:1, d:400},
{w:9000, h:3999, d:9191}
]
.map( box => box.w * box.h * box.d)
// [2000, 120, 1600, 330793281000]
items.filter((x) => x%2===0)
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
The new array will be the same size or smaller
Accepts a Function which...
optional arguments 2 and 3 are less commonly used
items.filter( x => x%2===0 )
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(x => x % 2 === 0);
[
"bacon cheeseburger",
"chicken sandwich",
"hamburger",
"bacon salad"
]
.filter(x => /bacon/i.test(x) );
items.reduce((acc, cur) => acc + cur, 0)
Applies a function against an accumulator and each element in the array to reduce it to a single value
Always results in a single item
the function accepts optional arguments 3 and 4, but they're less common
Accepts an initial accumulation value
items.reduce((acc, cur) => acc + cur, 0)
Accepts a Fuction which...
1
3
2
5
6
8
0
1
4
6
11
17
25
[1, 3, 2, 5, 6, 8].reduce(
(acc, cur) => acc + cur,
0
);
lab
Modern JavaScript
Template literals are string literals allowing embedded expressions. You can use multi-line strings and string interpolation features with them. They were called "template strings" in prior editions of the ES2015 / ES6 specification.
const price = 24.99;
const payments = 3;
const salesPitch = `Only ${payments} easy payments of $${price}!`
Note that the template literal is surrounded with back-ticks, not single quotes.
lab
Modern JavaScript
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.
/*
A function "add" with default
parameters which are used
if no values are passed
*/
const add = (a=1, b=2) => {
return a + b;
}
// call add with both parameters
console.log( add(3, 3) );
//call add with 1 missing parameter
console.log( add(3) );
//call add with no parameters
console.log( add() );
//call with one undefined parameter
console.log( add(undefined, 5) );
lab
Modern JavaScript
The destructuring assignment syntax is a JavaScript expression that makes it possible to extract data from arrays or objects into distinct variables.
const BigBird = { height: `8'2"`, color: '#f7f16d', age: 'timeless' };
const { color, height } = BigBird;
//color === '#f7f16d'
//height === `8'2"`
const colors = [ '#f00', '#0f0', '#00f' ];
const [ red, green ] = colors;
//red === '#f00'
//green === '#0f0'
const [,, blue] = colors;
//blue === '#00f'
"color", "height", "red", "green" and "blue" would all be variables and could be used just like if you declared
"const color = "#f7f16d;"
Values from an object can be destructured when that object is passed into a function as a parameter.
const render = ({title, description}) => {
console.log(title); // "..."
}
const bigObject = {
...
title: '...',
...
description: '...'
...
}
render(bigObject)
lab
Modern JavaScript
The spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.
const a = [ 1, 2, 3 ]
const flatArray = [ ...a, 4, 5 ];
//flatArray == [ 1, 2, 3, 4, 5 ]
const b = { a: 1, b: 2, c: 10, d: { a: 1 } };
const flatObject = { ...b, c: 3 };
//flatObject == { a: 1, b: 2, c: 3, d: { a:1 } };
When used in a destructuring assignment, the rest operator assigns the remaining (the rest of) values to a variable.
const numbers = [ 1, 2, 3, 4, 5 ];
const [ one, two, ...threeFourFive ] = numbers;
// one == 1
// two == 2
// threeFourFive === [ 3, 4, 5 ];
const data = { a: 1, b: 2, c: 4, d: 5 };
const { a, b, ...rest } = data;
// a == 1
// b == 2
// rest == { c: 4, d: 5 }
(({ a, b, z = 100, ...others }) => {
// a==1
// b==2
// z==100
// others == { c: 3, d: 4, e: 5}
})({ a: 1, b: 2, c: 3, d: 4, e: 5 })
lab
Modern JavaScript
Status | Code | |
---|---|---|
pending | getData() | ... |
... working ... | ||
resolved | .then(res => ...) | SUCCESS |
or | ||
rejected | .catch(err => ...) | ERROR |
Our App | Their Server |
---|
// mock functions
const displayHoorayMessage = ()=>{};
const queueWelcomeEmail = ()=>{};
const queueHandwrittenPostcard = ()=>{};
const doError = ()=>{};
// a function that returns a promise
signUpNewUser()
.then( displayHoorayMessage )
.then( queueWelcomeEmail )
.catch( doError )
.then( queueHandwrittenPostcard )
.catch( doError );
var p1 = new Promise( ( resolve, reject ) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise( ( resolve, reject ) => {
setTimeout(resolve, 2000, 2);
});
var p3 = new Promise( ( resolve, reject ) => {
setTimeout(resolve, 3000, 'three');
});
Promise.all([ p1, p2, p3 ]).then( values => {
console.log(values); // ["one", 2, "three"]
});
Promise.all returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first failed promise.
const results1 = Promise.resolve({ errors: false });
const results2 = Promise.reject({ errors: true });
any value can easily be converted into a promise
Async code that looks like synchronous code
// A bunch of functions
const displayHoorayMessage = () => {};
const queueWelcomeEmail = () => {};
const queueHandwrittenPostcard = () => {};
const doError = () => {};
const processNewUser = async () => {
try {
const data1 = await signUpNewUser();
const data2 = await queueWelcomeEmail(data1);
return queueHandwrittenPostcard(data2);
} catch (err) {
doError(err);
}
};
lab
Modern JavaScript
fetch('http://service.example.org/json')
.then(handleResponse)
.catch(handleError)
A configuration object can be optionally passed into the `fetch` call. This allows headers, and other options to be set on the request.
fetch('http://service.example.org/json', {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default',
credentials: 'same-origin'
})
fetch('http://service.example.org/json')
.then(res => res.ok ? res.json() : Promise.reject(res))
.then(res => {
//do something with the response
})
.catch(err => {
//do something with the error
});
fetch('http://service.example.org/json')
.then(res => res.ok ? res.json() : Promise.reject())
.then(({prop1, prop2}) => ({ prop1, prop2 }))
.then(res => {
//do stuff
})
.catch(res => console.log(res));
try {
const res = await fetch("http://service.example.org/json");
if (res.status === 401) {
// The request is unauthorized
redirectToLogin();
} else if (res.ok) {
return res.json();
} else {
throw Error(res.statusText);
}
} catch (e) {
displayError("Please try again");
}
lab
Modern JavaScript
class Shape {
constructor(center) {
this.center = center;
}
}
const myShape = new Shape({ x: 0, y: 0 });
class Circle extends Shape {
constructor(radius, center) {
super(center);
this.radius = radius;
}
area() {
return Math.PI * (this.radius * this.radius);
}
}
const myCircle = new Circle( 15, { x: 0, y: 0 });
myCircle.area(); // 706.8583470577034
Classes extend other classes, using prototypal inheritance. If a child class contains a constructor, it must also call the parent constructor, using the "super" function.
// before
class Shape {
constructor() {
this.name = "Shape";
this.color = "Blue";
}
}
An extension to JS classes (stage3) allows for instance properties
// after
class Shape {
name = "Shape";
color = "Blue";
}
lab
Modern JavaScript
Part 2
ReactJS
Platform agnostic library (react)
Targets
react
react-dom
ReactJS
JS
CSS
HTML
CSS
JS
HTML
in a page model
/page-1
/page-2
JS
CSS
HTML
in a component model
Button, DatePicker, Modal, List, List-Item, Media
Button, DatePicker, Modal, List, List-Item, Media
Button, DatePicker, Modal, List, List-Item, Media
JS
CSS
HTML
in a component model
Button
DatePicker
Modal
List
ListItem
Media
in this model, how a components fulfills its defined responsibility is an encapsulated, implementation detail
💯
is a component
Tile
<Tile
title=""
subTitle=""
backgroundImageSrc=""
href=""
/>
AsideSection
are components
Split
also a component
<Split ratio="1/3" gapSize="xs">
<aside>I'm a distraction</aside>
<main>I'm the main content</main>
</Split>
are components
<Network
render={({ online }) =>
<p>You are online: {online ? 'Yep' : 'Nope'}.</p>
}
/>
as components
<Fetch
url={url}
onData={handleData}
onError={handleError}
/>
are components
"good components" do one thing, do it well, are reusable, and are composable
ReactJS
const name = "Tacos";
const when = "always";
const thingToRender = (
<section>
<p>What should we eat: {name}</p>
<p>When should we them: {when}</p>
</section>
);
<input onChange={console.log} />
<div className="hello">hi</div>
<label htmlFor="otherId">hi</label>
<ul>
{[0, 1, 2].map( num =>
<li key={num}>{num}</li>
)}
</ul>
<input defaultValue="hello" />
<input />
<br />
<hr />
<meta />
<div>
{ Math.random() > 0.5 && <span>you win</span> }
</div>
<div>{ functionThatReturnsRenderable() }</div>
{ 5 > 3
? <strong>yes</strong>
: <small>no</small>
}
{values.map(n => <strong>{n}</strong>)}
<date>
{(new Date()).toLocaleString()}
</date>
lab
ReactJS
A function which returns JSX is called a component. Component names must be capitalized.
//Create Component
const MyComponent = () => (
<h1>A nice static component!</h1>
);
//Render the component
ReactDOM.render(
<MyComponent />,
document.getElementById("app")
);
A component could also include a function body
//Create Component
const MyComponent = () => {
// do stuff before returning JSX element
return (
<h1>A nice static component!</h1>
)
};
//Render the component
ReactDOM.render(
<MyComponent />,
document.getElementById("app")
);
//Create Component
const Greet = (props) => (
<h1>Hello, {props.name}!</h1>
);
//Create Component
const Greet = ({ name }) => (
<h1>Hello, {name}!</h1>
);
strings
//Render the component
ReactDOM.render(
<Greet name="Alice" />,
document.getElementById("app")
);
integers
//Render the component
ReactDOM.render(
<Greet age={42} />,
document.getElementById("app")
);
objects
//Render the component
ReactDOM.render(
<Greet data={{ name: "Alice", gender: "Female"}} />
document.getElementById("app")
);
function reference
const callback = () => { console.log('Hello World') };
//Render the component
ReactDOM.render(
<Greet onClick={callback} />,
document.getElementById("app")
);
arrays
const people = ["Albert", "Scott"];
//Render the component
ReactDOM.render(
<Greet items={people} />,
document.getElementById("app")
);
const person = {name: "Alice", gender: "Female"};
//Render the component
ReactDOM.render(
<Greet { ...person } />,
document.getElementById("app")
);
// <Greet { ...person } /> === <Greet name="Alice" gender="Female" />
🎇 You Name It! 🎇
as long as it's an expression
lab
ReactJS
Inline Styles use object literal notation in JSX
//Declare properties using camelCase
const myHeaderStyle = {
paddingTop:'1em',
fontFamily:'Palatino',
backgroundColor:'aliceblue'
};
const MyHeader=()=>(
<h3 style={myHeaderStyle}>
Styling in React
</h3>
);
Don't do this (generally)
Due to 'class' being a reserved word, you need to use 'className'
//In a separate Style Sheet
.header{
padding-top : 1em;
font-family : Palatino;
background-color : aliceblue;
};
const MyHeader=()=>(
<h3 className="header">
Styling in React
</h3>
);
import './my-header.css';
const MyHeader=()=>(
<h3 className="header">
Styling in React
</h3>
);
/* my-header.css */
.header {
font-family: "Comic Sans"
}
one "imported" .css file per component
like import css but no name collisions
import styles from './my-header.css';
const MyHeader=()=>(
<h3 className={styles.header}>
Styling in React
</h3>
);
/* my-header.css */
.header {
font-family: "Comic Sans"
}
💪
CSS is written in the JS and the CSS is injected into the styled tag in the head of the document
import styled from 'styled-components';
const Header = styled.h3`
padding-top : 1em;
font-family : Palatino;
background-color : aliceblue;
`;
const MyHeader= () => (
<Header>
Styling in React
</Header>
);
lab
ReactJS
Pass functions instead of strings
<!-- The HTML Way -->
<button onclick="recordLike()">
Click if you like!
</button>
// The React Way
<button onClick={this.recordLike}>
Click if you like!
</button>
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.props.thankYouMessage);
}
render () {
return (
<button onClick={this.handleClick}>
Like
</button>
);
}
}
class LikeButton extends React.Component {
// Without Binding...
//constructor(props) {
// super(props);
// this.handleClick = this.handleClick.bind(this);
//}
handleClick() {
console.log(this.props.thankYouMessage);
// ...Uncaught TypeError: Cannot read property
// 'thankYouMessage' of undefined
}
render () {
return (
<button onClick={this.handleClick}>
Like
</button>
);
}
}
Gotcha: Bind member functions for the correct 'this'
class LikeButton extends React.Component {
// No binding needed. `handleClick` is auto-bound
// to LikeButton's `this`.
handleClick = () =>
console.log(this.props.thankYouMessage);
render () {
return (
<button onClick={this.handleClick}>
Like
</button>
);
}
}
... Or just use the Class Field syntax
const ActionLink = () => {
const handleClick = (event) => {
// 'event' has lots of useful data
console.log(event.target.href); // logs "#"
console.log(event.target.nodeName) // logs "A"
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
See also: React Synthetic Events, Native DOM Events
<!-- The HTML Way -->
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
Use e.preventDefault() instead of "return false;"
// The React Way
const ActionLink = () => {
const handleClick = e => {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
lab
ReactJS
class Counter extends React.Component {
state = {
count: 0
}
handleReset = () =>
this.setState({ count: 0 });
handleIncrement = () => {
this.setState(state => ({
count: state.count + 1
}))
}
render() {
const { count } = this.state;
return (
<div>
<strong>{count}</strong>
<button onClick={this.handleReset}>reset</button>
<button onClick={this.handleIncrement}>+</button>
</div>
)
}
}
class Counter extends React.Component {
state = {
results: []
}
handleFetch = () => {
fetch("http://example.org/something")
.then( res => res.json() )
.then( results => this.setState({ results }) )
}
render() {
const { results } = this.state;
return (
<div>
<button onClick={this.handleFetch}>Fetch!</button>
{results.map(item => <Other {...item} />)}
</div>
)
}
}
lab
ReactJS
Each React component has several “lifecycle methods” that you can override to run code at particular times in the process.
These methods are called when an instance of a component is being created and inserted into the DOM:
An update can be caused by changes to props or state. These methods are called when a component is being re-rendered:
This method is called when a component is being removed from the DOM:
React Lifecycle Methods Diagram
This lifecycle method is called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.
These methods are being gradually phased out
import React, { Component } from "react";
import fetch from "isomorphic-unfetch";
const api = "https://api.github.com/repos/zeit/next.js"
class HomePage extends Component {
static async getInitialProps() {
const response = await fetch(api);
const json = await response.json();
return { stars: json.stargazers_count };
}
render() {
return (
<div>
<p>Next.js has {this.props.stars} ⭐️</p>
</div>
);
}
}
export default HomePage;
lab
ReactJS
HTML Forms typically manage their own state. For example, a text element contains it's own value (state).
React form elements can be either controlled or uncontrolled.
The value of form elements are controlled via state.
class Form extends React.Component {
state = {
value: ""
}
handleChange = e =>
this.setState({ value: e.target.value });
render() {
const { value } = this.state;
return (
<input onChange={this.handleChange} value={value} />
);
}
}
This allows the conversion of user input into data of any shape and allows for friendlier user feedback and validation.
lab
ReactJS
class AutoFocus extends React.Component {
myRef = React.createRef();
componentDidMount = () => {
this.myRef.current.focus();
}
render() {
return <input ref={this.myRef} />
}
}
class AutoFocus extends React.Component {
handleRef = element => {
this.textInput = element;
};
componentDidMount = () => {
this.textInput.focus();
}
render() {
return <input ref={this.handleRef} />
}
}
class AutoFocus extends React.Component {
...
render() {
return <input ref="inputRef" />
}
}
sometimes you'll need to pass a ref through to a child
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
lab
ReactJS
provide a single source of truth
lab
ReactJS
Component A takes a thing to render as a prop, does its work, and then renders said thing with results of its work.
<Network
render={({ online }) =>
<p>You are online: {online ? 'Yep' : 'Nope'}.</p>
}
/>
example via react-network
https://github.com/ReactTraining/react-network
export default class Network extends Component {
static defaultProps = {
render: () => null,
onChange: () => {}
}
state = {
online: window.navigator.onLine
}
componentDidMount() {
window.addEventListener("offline", this.handleChange)
window.addEventListener("online", this.handleChange)
this.props.onChange(this.state)
}
componentWillUnmount() {
window.removeEventListener("offline", this.handleChange)
window.removeEventListener("online", this.handleChange)
}
handleChange = () => {
const online = window.navigator.onLine
this.props.onChange({ online })
this.setState({ online })
}
render() {
return this.props.render(this.state)
}
}
<Network>
{({ online }) =>
<p>You are online: {online ? 'Yep' : 'Nope'}.</p>
}}
</Network>
sometimes: "Render-Callbacks"
lab
ReactJS
React is great at creating visual components, but that's just scratching the surface
<MediaQuery
query="(min-width: 800px)"
onChange={this.handleScreenChange}
/>
<LockScreen active />
<Hotkey
combo="Ctrl+p"
onHotkey={this.handleHotkey}
/>
lab
ReactJS
Many components need strings.
const ComponentA = ({strings}) => (
<h1>{strings.welcomeMessage}</h1>
);
const ComponentB = ({strings}) => (
<h1>{strings.goodByeMessage}</h1>
);
A HOC that specializes in providing strings
const withStrings = Component => {
const strings = magicallyGetStrings();
return props => (
<Component
{...props}
strings={strings}
/>
);
}
A HOC that provides fetch data
const withFetchData = url => Component => {
return class extends React.Component {
state = { data: null };
componentDidMount() {
fetch(url)
.then( res => res.json() )
.then( data => this.setState({ data }) )
}
render() {
const { data } = this.state;
return !data
? null
: (
<Component
{...this.props}
data={data}
/>
);
}
}
}
lab
ReactJS
const Modal = ({ active, children, onCloseGesture, ...props }) => (
<ModalMask {...props} active={active} onClick={onCloseGesture}>
<Lock active={active} />
<KeyDown keys={{ Escape: active && onCloseGesture }}>
<Card onClick={capture} onKeyDown={capture}>
{children}
</Card>
</KeyDown>
</ModalMask>
);
explain this?
the final project is this lab 🙂
Part 3
import React, { Component } from "react";
class Counter extends Component {
state = { value: 0 };
increment = () => {
this.setState(previousState => ({
value: previousState.value + 1
}));
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
</div>
);
}
}
import React, { Component } from "react";
import fetch from "isomorphic-unfetch";
const api = "https://api.github.com/repos/zeit/next.js"
class HomePage extends Component {
static async getInitialProps() {
const response = await fetch(api);
const json = await response.json();
return { stars: json.stargazers_count };
}
render() {
return (
<p>Next.js has {this.props.stars} ⭐️</p>
);
}
}
The Starter doesn't have one by default because not all apps need one
REST back-end
GraphQL back-end
Data Management
Data Management
all Redux State is stored in a single object
const state = {
books: {
id1: {
id: "id1",
name: "Green Eggs and Ham"
}
},
claps: {
id1: 10
}
};
no setters. changes are made by emitting actions describing what happened.
const action = {
type: "BOOK_CLAP",
payload: {
id: "id1"
}
}
actions flow through reducers which optionally produce new state
const claps = ( state = {}, { type, payload } ) => {
switch( type ) {
case "BOOK_CLAP": {
const { id } = payload;
return {
...state,
[id]: state[id] ? state[id] + 1 : 1
}
}
default:
return state
}
}
Data Management
const action = {
type: "BOOK_CLAP",
payload: {
id: "id1"
}
}
const createClap = (id) => ({
type: "BOOK_CLAP",
payload: { id }
});
type is required
const claps = ( state = {}, { type, payload } ) => {
switch( type ) {
case "BOOK_CLAP": {
const { id } = payload;
return {
...state,
[id]: state[id] ? state[id] + 1 : 1
}
}
default:
return state
}
}
always return new memory reference when change occurs
const selectBookById = (state, id) => {
const book = state.books[id];
const claps = state.claps[id] || 0;
return {
...book,
claps
}
}
optional but recommended helpers to grabbing parts from state
import { createStore, combineReducers } from "redux";
import { books, claps } from "./reducers";
import { selectBookById } from "./selectors";
import createClap from "./actions";
const rootReducer = combineReducers({ books, claps });
const store = createStore(rootReducer);
const unsubscribe = store.subscribe( () => {
const state = store.getState();
console.log( selectBookById(state, "id1") );
});
store.dispatch( createClap("id1") );
unsubscribe();
lab
Data Management
Notifies subscribers when its state changes
Changes the UI when props or state changes
import React from "react";
import store "./app/store";
class ConnectedComponent extends React.Component {
componentDidMount() {
this.unsub = store.subscribe( () => {
const state = store.getState();
const stuffICareAbout = selectMyStuff(state);
this.setState({stuffICareAbout});
})
}
componentWillUnmount() {
this.unsub();
}
// ...
}
aka naive
import { connect } from "react-redux";
import { createClap } from "./app/store/actions";
import { selectBooks } from "./app/store/selectors";
import BookView from "./app/components/BookView";
const mapStateToProps = state => ({
books: selectBooks(state)
});
const bindActionsToDispatch = { createClap };
const storeConnector = connect(
mapStateToProps,
bindActionsToDispatch
);
export default storeConnector( BookView );
the connect HoC
// in one of your NextJS pages
import withRedux from "next-redux-wrapper";
import { initStore } from "./app/store/storeFactory";
import { createClap } from "./app/store/actions";
import { selectBooks } from "./app/store/selectors";
// Your page component...
const View = () => <div>I am app</div>;
const mapStateToProps = state => ({
books: selectBooks(state)
});
const bindActionsToDispatch = { createClap };
export default withRedux(
initStore,
mapStateToProps,
bindActionsToDispatch
)(View);
the withRedux HoC
Data Management
Manages your data for you
*if you have a GraphQL back-end
Apollo is a lot simpler + faster than Redux
If possible, try to get or create a GraphQL back-end so you can use Apollo
type Query {
books(search: String): [Book!]
}
type Book {
id: ID!
volumeInfo: VolumeInfo!
}
type VolumeInfo {
title: String!
authors: [String!]
imageLinks: ImageLinks
}
type ImageLinks {
smallThumbnail: String
thumbnail: String
}
{
books {
id
volumeInfo {
title
}
}
}
Can use any fields in schema
const api = "https://react-bootcamp-labs-api.app.lds.org";
const response = await fetch(api, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `
{
books {
id
volumeInfo {
title
}
}
}
`
})
});
const json = await response.json();
console.log(json);
{
"books": [
{
"id": "1",
"volumeInfo": {
"title": "Bible"
}
},
{
"id": "2",
"volumeInfo": {
"title": "Book of Mormon"
}
}
...
]
}
import React from "react";
import { H3 } from "@lds/eden-headings";
const Books = ({ books }) => (
<section>
{books.map(book => (
<div key={book.id}>
<H3>{book.volumeInfo.title}</H3>
</div>
))}
</section>
);
export default Books;
import React from "react";
import fetch from "isomorphic-unfetch";
import Books from "components/Books";
export default class SomePage extends React.Component {
static async getInitialProps() {
const response = await fetch(
"https://react-bootcamp-labs-api.app.lds.org",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `
graphql syntax here...
`
})
}
);
const json = await response.json();
return { books: json.data.books };
}
render() {
console.log(this.props.books);
return <Books books={this.props.books} />;
}
}
{
books {
id
volumeInfo {
title
}
}
}
import gql from "graphql-tag";
const GET_BOOKS = gql`
{
books {
id
volumeInfo {
title
}
}
}
`;
import React from "react";
import { Query } from "react-apollo";
import Books from "components/Books";
const BooksWithData = () => (
<Query query={graphql syntax here...}>
{({ loading, error, data }) => {
if (loading) return "Loading...";
if (error) return "Error!";
return <Books books={data.books} />;
}}
</Query>
);
import React from "react";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import Books from "components/Books";
const GET_BOOKS = gql`
{
books {
id
volumeInfo {
title
}
}
}
`;
const BooksWithData = () => (
<Query query={GET_BOOKS}>
{({ loading, error, data }) => {
if (loading) return "Loading...";
if (error) return "Error!";
return <Books books={data.books} />;
}}
</Query>
);
Used in lab for simplicity
Recommended for real projects using React Starter
lab
lab
User searches for a title
Fetches data from Google Book API
Displays Book Data in Tile
<App />
<Heading />
<BookSearch />
<Stack />
<Tile />
<QuickForm />
<Icon />
don't worry about Redux/Apollo unless you finish early