React: Difference between revisions
No edit summary |
|||
(5 intermediate revisions by the same user not shown) | |||
Line 5: | Line 5: | ||
A template for a project can be found https://github.com/jscomplete/rgs-template | A template for a project can be found https://github.com/jscomplete/rgs-template | ||
= Production Setup = | = Production Setup = | ||
Below are examples of dev vs production scripts | |||
=== Packages build scripts === | === Packages build scripts === | ||
Additional changes to NPM build scripts for production | Additional changes to NPM build scripts for production | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="js"> | ||
"test:ci": "jest", | "test:ci": "jest", | ||
"clean:build": "rimraf ./build && mkdir build", | "clean:build": "rimraf ./build && mkdir build", | ||
Line 16: | Line 17: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Webpack | === Webpack differences === | ||
Production | Production | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="js"> | ||
const webpack = require("webpack"); | const webpack = require("webpack"); | ||
const path = require("path"); | const path = require("path"); | ||
Line 152: | Line 153: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Source switches === | ===Source switches=== | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="js"> | ||
// Use CommonJS require below so we can dynamically import during build-time. | // Use CommonJS require below so we can dynamically import during build-time. | ||
if (process.env.NODE_ENV === "production") { | if (process.env.NODE_ENV === "production") { | ||
Line 161: | Line 162: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=Variables= | =Variables= | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="js" line="line"> | ||
const myVar = ( | const myVar = ( | ||
<h1>Love it</h1> | <h1>Love it</h1> | ||
Line 182: | Line 176: | ||
This is because you have not bound the function in the class to this e.g. | This is because you have not bound the function in the class to this e.g. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="js"> | ||
this.onTitleChange = this.onTitleChange.bind(this); | this.onTitleChange = this.onTitleChange.bind(this); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 189: | Line 183: | ||
== Classes == | == Classes == | ||
Below is the old class approach with binding and constructor | Below is the old class approach with binding and constructor | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
class InputPage extends React.Component { | class InputPage extends React.Component { | ||
Line 229: | Line 223: | ||
And now old class approach without binding and constructor and using spread | And now old class approach without binding and constructor and using spread | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
class InputPage extends React.Component { | class InputPage extends React.Component { | ||
Line 265: | Line 259: | ||
Example of setting state | Example of setting state | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const InputElement = () => { | const InputElement = () => { | ||
Line 280: | Line 274: | ||
You can get a reference from the DOM using UseRef | You can get a reference from the DOM using UseRef | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const imageRef = useRef(null) | const imageRef = useRef(null) | ||
Line 300: | Line 294: | ||
Add and remove scroll handler | Add and remove scroll handler | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
useEffect( () = > { | useEffect( () = > { | ||
window.addEventListener("scroll", scrollHandler) | window.addEventListener("scroll", scrollHandler) | ||
Line 313: | Line 307: | ||
e.g. Maybe a checkbox value | e.g. Maybe a checkbox value | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const [checkboxValue, setCheckboxValue] = useState(false) | const [checkboxValue, setCheckboxValue] = useState(false) | ||
Line 332: | Line 326: | ||
Top Level, wraps a context around pages | Top Level, wraps a context around pages | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
import React from "react"; | import React from "react"; | ||
import Home from "./Home"; | import Home from "./Home"; | ||
Line 361: | Line 355: | ||
A the usage on the page. | A the usage on the page. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
import React, { useState, useEffect, useContext} from "react"; | import React, { useState, useEffect, useContext} from "react"; | ||
import { AppContext } from "./App" | import { AppContext } from "./App" | ||
Line 378: | Line 372: | ||
UseState is built on Reducer i.e. | UseState is built on Reducer i.e. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const [speakerList, setSpeakerList] = useState([]) | const [speakerList, setSpeakerList] = useState([]) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 384: | Line 378: | ||
Is the same as | Is the same as | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const [speakerList, setSpeakerList] = useReducer( (state,action) => action, []) | const [speakerList, setSpeakerList] = useReducer( (state,action) => action, []) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 390: | Line 384: | ||
Is the same as | Is the same as | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
function speakerReducer(state, action) { | function speakerReducer(state, action) { | ||
return action | return action | ||
Line 400: | Line 394: | ||
We can now make this work the same a the useState | We can now make this work the same a the useState | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
function speakerReducer(state, action) { | function speakerReducer(state, action) { | ||
switch(action.type) { | switch(action.type) { | ||
Line 418: | Line 412: | ||
So the old usage was | So the old usage was | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
setSpeakerList(mySpeakerList) | setSpeakerList(mySpeakerList) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Using the reducer gives | Using the reducer gives | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
dispatch( { | dispatch( { | ||
type: "setSpeakerList", | type: "setSpeakerList", | ||
Line 433: | Line 427: | ||
This caches a function. Given the function below, | This caches a function. Given the function below, | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const heartFavoriteHandler = (e, favoriteValue) => { | const heartFavoriteHandler = (e, favoriteValue) => { | ||
e.preventDefault(); | e.preventDefault(); | ||
Line 446: | Line 440: | ||
We can cache this using UseCallback | We can cache this using UseCallback | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const heartFavoriteHandler = useCallback( (e, favoriteValue) => { | const heartFavoriteHandler = useCallback( (e, favoriteValue) => { | ||
e.preventDefault(); | e.preventDefault(); | ||
Line 459: | Line 453: | ||
However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail | However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
<div className="card-deck"> | <div className="card-deck"> | ||
{speakerListFiltered.map( | {speakerListFiltered.map( | ||
Line 481: | Line 475: | ||
so this gave | so this gave | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const SpeakerDetail = React.memo( ({ | const SpeakerDetail = React.memo( ({ | ||
Line 537: | Line 531: | ||
Similarly you can cache a value using UseMemo. e.g. | Similarly you can cache a value using UseMemo. e.g. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const mySortedSpeakerList = useMemo( ()=> speakerList | const mySortedSpeakerList = useMemo( ()=> speakerList | ||
.filter( | .filter( | ||
Line 558: | Line 552: | ||
An example of a reducer with a custom hook using axios | An example of a reducer with a custom hook using axios | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const dataFetchReducer = (state, action) => { | const dataFetchReducer = (state, action) => { | ||
switch (action.type) { | switch (action.type) { | ||
Line 596: | Line 590: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="jsx"> | ||
const useAxiosFetch = (initialUrl, initialData) => { | const useAxiosFetch = (initialUrl, initialData) => { | ||
const [url] = useState(initialUrl); | const [url] = useState(initialUrl); |
Latest revision as of 01:45, 12 July 2021
Quick Reference
Development Setup
A good resource for this can be found https://jscomplete.com/learn/1rd-reactful' by Samer Buna
A template for a project can be found https://github.com/jscomplete/rgs-template
Production Setup
Below are examples of dev vs production scripts
Packages build scripts
Additional changes to NPM build scripts for production
"test:ci": "jest",
"clean:build": "rimraf ./build && mkdir build",
"prebuild": "run-p clean:build test:ci",
"build": "webpack --config webpack.config.prod.js",
"postbuild": "run-p start:api serve:build",
"serve:build": "http-server ./build"
Webpack differences
Production
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpackBundleAnalyzer = require("webpack-bundle-analyzer");
process.env.NODE_ENV = "production";
module.exports = {
mode: "production",
target: "web",
devtool: "source-map",
entry: "./src/index",
output: {
path: path.resolve(__dirname, "build"),
publicPath: "/",
filename: "bundle.js"
},
plugins: [
// Display bundle stats
new webpackBundleAnalyzer.BundleAnalyzerPlugin({ analyzerMode: "static" }),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
}),
new webpack.DefinePlugin({
// This global makes sure React is built in prod mode.
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.env.API_URL": JSON.stringify("http://localhost:3001")
}),
new HtmlWebpackPlugin({
template: "src/index.html",
favicon: "src/favicon.ico",
minify: {
// see https://github.com/kangax/html-minifier#options-quick-reference
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"]
},
{
test: /(\.css)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
sourceMap: true
}
},
{
loader: "postcss-loader",
options: {
plugins: () => [require("cssnano")],
sourceMap: true
}
}
]
}
]
}
};
Development
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
process.env.NODE_ENV = "development";
module.exports = {
mode: "development",
target: "web",
devtool: "cheap-module-source-map",
entry: "./src/index",
output: {
path: path.resolve(__dirname, "build"),
publicPath: "/",
filename: "bundle.js"
},
devServer: {
stats: "minimal",
overlay: true,
historyApiFallback: true,
disableHostCheck: true,
headers: { "Access-Control-Allow-Origin": "*" },
https: false
},
plugins: [
new webpack.DefinePlugin({
"process.env.API_URL": JSON.stringify("http://localhost:3001")
}),
new HtmlWebpackPlugin({
template: "src/index.html",
favicon: "src/favicon.ico"
})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"]
},
{
test: /(\.css)$/,
use: ["style-loader", "css-loader"]
}
]
}
};
Source switches
// Use CommonJS require below so we can dynamically import during build-time.
if (process.env.NODE_ENV === "production") {
module.exports = require("./configureStore.prod");
} else {
module.exports = require("./configureStore.dev");
}
Variables
const myVar = (
<h1>Love it</h1>
);
Common Errors
When using classes and you have forgotten to bind you get
Cannot read property 'setState' of undefined
This is because you have not bound the function in the class to this e.g.
this.onTitleChange = this.onTitleChange.bind(this);
Form Example
Classes
Below is the old class approach with binding and constructor
class InputPage extends React.Component {
constructor(props) {
super(props);
this.state = {
course : {
title: ""
}
};
this.onTitleChange = this.onTitleChange.bind(this);
}
onTitleChange(event) {
this.setState({
course: {
title: event.target.value,
},
});
}
render() {
return (
<form >
<input
type="text"
value={this.state.course.title}
onChange={this.onTitleChange}
/>
</form>
);
}
}
And now old class approach without binding and constructor and using spread
class InputPage extends React.Component {
this.state = {
course : {
title: ""
}
}
onTitleChange = event => {
const course = {...this.state.course, title: event.target.value}
this.setState( {course} )
}
render() {
return (
<form >
<input
type="text"
value={this.state.course.title}
onChange={this.onTitleChange}
/>
</form>
)
}
Hooks
Hooks can only be used at the top level. You can set rules in your editor to ensure this.
UseState
Example of setting state
const InputElement = () => {
const [inputText, setInputText] = useState("")
return (
<input placeholder="some text" onChange={(event) => { setInputText(event.target.value) } } />
)
}
UseRef
You can get a reference from the DOM using UseRef
const imageRef = useRef(null)
console.info("Got here")
return (
<img
onMouseOver={ ()=>{ imageRef.current.src = secondaryImg} }
onMouseOut={ ()=>{ imageRef.current.src = primaryImg} }
src={primaryImg}
ref={imageRef}
/>
)
UseEffect
The first argument to useEffect is a function which executes when the component first mounts. The first argument of the return should be a function which remove the resources, if applicable, used as the first argument. e.g.
Add and remove scroll handler
useEffect( () = > {
window.addEventListener("scroll", scrollHandler)
return ( ()=> {
window.removeEventListener("scroll", scrollHandler)
})
})
The second argument is a array of values which, if changed, will cause the effect to be called again.
e.g. Maybe a checkbox value
const [checkboxValue, setCheckboxValue] = useState(false)
useEffect( () = > {
window.addEventListener("scroll", scrollHandler)
return ( ()=> {
window.removeEventListener("scroll", scrollHandler)
}, [checkboxValue])
})
The effect will be called each time the checkbox is clicked.
UseConfig
This allows passing of config to different pages.
Top Level, wraps a context around pages
import React from "react";
import Home from "./Home";
import Speakers from "./Speakers";
export const AppContext = React.createContext();
const pageToShow = pageName => {
if (pageName === "Home") return <Home />;
if (pageName === "Speakers") return <Speakers />;
return <div>Not Found</div>;
};
const configValue = {
showSpeakerSpeakingDays : true
}
const App = ({ pageName }) => {
return <AppContext.Provider value={configValue}>
<div>{pageToShow(pageName)}</div>
</AppContext.Provider>
};
export default App;
A the usage on the page.
import React, { useState, useEffect, useContext} from "react";
import { AppContext } from "./App"
const PageFunction = ({}) => {
const context = useContext(AppContext)
}
UseReducer
A reducer is defined as something which takes a previous state and function and returns a new state
(previousState, action) => newState
UseState is built on Reducer i.e.
const [speakerList, setSpeakerList] = useState([])
Is the same as
const [speakerList, setSpeakerList] = useReducer( (state,action) => action, [])
Is the same as
function speakerReducer(state, action) {
return action
}
const [speakerList, setSpeakerList] = useReducer( speakerReducer, [])
We can now make this work the same a the useState
function speakerReducer(state, action) {
switch(action.type) {
case "setSpeakerList" : {
return action.data;
}
default:
return state;
}
}
// So replaced setSpeakList, which is not returned with a dispatch name, we called it dispatch but we could have called derek
// const [speakerList, setSpeakerList] = useReducer( speakerReducer, [])
const [speakerList, dispatch] = useReducer( speakerReducer, [])
So the old usage was
setSpeakerList(mySpeakerList)
Using the reducer gives
dispatch( {
type: "setSpeakerList",
data: mySpeakerList
})
UseCallback
This caches a function. Given the function below,
const heartFavoriteHandler = (e, favoriteValue) => {
e.preventDefault();
const sessionId = parseInt(e.target.attributes["data-sessionid"].value);
dispatch({
type : favoriteValue === true ? "setFavourite" : "setUnfavourite",
sessionId
});
We can cache this using UseCallback
const heartFavoriteHandler = useCallback( (e, favoriteValue) => {
e.preventDefault();
const sessionId = parseInt(e.target.attributes["data-sessionid"].value);
dispatch({
type : favoriteValue === true ? "setFavourite" : "setUnfavourite",
sessionId
}, [] );
However for this to work you will need to wrap the content it is reloading in React.memo. In this case it was SpeakerDetail
<div className="card-deck">
{speakerListFiltered.map(
({ id, firstName, lastName, bio, favorite }) => {
return (
<SpeakerDetail
key={id}
id={id}
favorite={favorite}
onHeartFavoriteHandler={heartFavoriteHandler}
firstName={firstName}
lastName={lastName}
bio={bio}
/>
);
}
)}
</div>
so this gave
const SpeakerDetail = React.memo( ({
id,
firstName,
lastName,
favorite,
bio,
onHeartFavoriteHandler
}) => {
console.log(`SpeakerDetail:${id} ${firstName} ${lastName} ${favorite}`);
return (
<div className="card col-4 cardmin">
<ImageToggleOnScroll
className="card-img-top"
primaryImg={`/static/speakers/bw/Speaker-${id}.jpg`}
secondaryImg={`/static/speakers/Speaker-${id}.jpg`}
alt="{firstName} {lastName}"
/>
<div className="card-body">
<h4 className="card-title">
<button
data-sessionid={id}
const mySortedSpeakList = useMemo( ()=> speakerList
.filter(
({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
)
.sort(function(a, b) {
if (a.firstName < b.firstName) {
return -1;
}
if (a.firstName > b.firstName) {
return 1;
}
return 0;
}), [speakingSaturday,speakingSunday,speakerList]); className={favorite ? "heartredbutton" : "heartdarkbutton"}
onClick={e => {
onHeartFavoriteHandler(e, !favorite);
}}
/>
<span>
{firstName} {lastName}
</span>
</h4>
<span>{bio}</span>
</div>
</div>
);
});
UseMemo
Similarly you can cache a value using UseMemo. e.g.
const mySortedSpeakerList = useMemo( ()=> speakerList
.filter(
({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
)
.sort(function(a, b) {
if (a.firstName < b.firstName) {
return -1;
}
if (a.firstName > b.firstName) {
return 1;
}
return 0;
}), [speakingSaturday,speakingSunday,speakerList]);
The value will only change if one of the speakingSaturday,speakingSunday,speakerList changes
Custom Hook
An example of a reducer with a custom hook using axios
const dataFetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return { ...state, isLoading: true, isError: false };
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: action.payload
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: "Data Retrieve Failure"
};
case "REPLACE_DATA":
// The record passed (state.data) must have the attribute "id"
const newData = state.data.map(rec => {
return rec.id === action.replacerecord.id ? action.replacerecord : rec;
});
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: newData
};
default:
throw new Error();
}
};
const useAxiosFetch = (initialUrl, initialData) => {
const [url] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
hasErrored: false,
errorMessage: "",
data: initialData
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: "FETCH_INIT" });
try {
let result = await axios.get(url);
if (!didCancel) {
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
}
} catch (err) {
if (!didCancel) {
dispatch({ type: "FETCH_FAILURE" });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
const updateDataRecord = record => {
dispatch({
type: "REPLACE_DATA",
replacerecord: record
});
};
return { ...state, updateDataRecord };
};
Maps
// Only do this if items have no stable IDs
const todo = ["fix", "syntax", "highlighting"];
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);
Component Example
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {name: "Frarthur"};
}
render() {
return <div></div>;
}
};
Passing Props to child
Parent.json
import React from 'react';
import ReactDOM from 'react-dom';
import { Child } from './Child';
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { name: 'Frarthur' };
}
render() {
return <Child name={this.state.name} />;
}
}
ReactDOM.render(<Parent />, document.getElementById('app'));
Child.json
import React from 'react';
export class Child extends React.Component {
render() {
return <h1>Hey, my name is {this.props.name}!</h1>;
}
}
Forms with Ref
You can capture import using the React.createRef() e.g.
class Form extends React.Component
{
userNameInput = React.createRef();
handleSubmit = (event) => {
event.preventDefault()
console.log(userNameInput.current.value)
}
render()
{
return (
<form onSubmit={this.handleSubmit} >
<input
type="text"
placeHolder="background text"
ref={this.userNameInput}
required
/>
/>
)
}
}
Forms without Ref
A Better way may be to use a state to hold the name.
class Form extends React.Component
{
state = { userName : ''}
render()
{
return (
<form onSubmit={this.handleSubmit} >
<input
type="text"
value={this.state.userName}
OnChange = {
event => {
this.setState( {userName: event.target.value} )
}
}
required
/>
/>
)
}
}
Binding a function
import React from 'react';
import ReactDOM from 'react-dom';
import { Video } from './Video';
import { Menu } from './Menu';
const VIDEOS = {
fast: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-fast.mp4',
slow: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-slow.mp4',
cute: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-cute.mp4',
eek: 'https://s3.amazonaws.com/codecademy-content/courses/React/react_video-eek.mp4'
};
class App extends React.Component {
constructor(props) {
this.chooseVideo = this.chooseVideo.bind(this);
super(props);
this.state = {
src: VIDEOS.fast
};
}
chooseView(newView) {
this.setState({
src: VIDEOS[newVideo]
});
}
render() {
return (
<div>
<h1>Video Player</h1>
<Menu chooseVideo={this.chooseView()} />
<Video src={this.state.src} />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
import React from 'react';
export class Menu extends React.Component {
constructor() {
super(props)
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
var text = e.target.value;
this.props.chooseView(text);
}
render() {
return (
<form OnClick={this.handleClick}>
<input type="radio" name="src" value="fast" /> fast
<input type="radio" name="src" value="slow" /> slow
<input type="radio" name="src" value="cute" /> cute
<input type="radio" name="src" value="eek" /> eek
</form>
);
}
}
import React from 'react';
export class Video extends React.Component {
render() {
return (
<div>
<video controls autostart autoPlay muted src={this.props.src} />
</div>
);
}
}
Inline Styling
React needs double quotes to inject style in JSX e.g.
import React from 'react';
import ReactDOM from 'react-dom';
const styleMe = <h1 style={{ background: 'lightblue', color: 'darkred' }}>Please style me! I am so bland!</h1>;
ReactDOM.render(
styleMe,
document.getElementById('app')
);
Another example
mport React from 'react';
import ReactDOM from 'react-dom';
const styles = {
background: 'lightblue',
color: 'darkred',
marginTop: '100px',
fontSize: '50px'
}
const styleMe = <h1 style={styles}>Please style me! I am so bland!</h1>;
ReactDOM.render(
styleMe,
document.getElementById('app')
);
You can write px styles without quotes e.g.
const styles = {
background: 'lightblue',
color: 'darkred',
marginTop: 100,
fontSize: 50
}
Stateless Classes
A component class written in the usual way:
export class MyComponentClass extends React.Component {
render() {
return <h1>Hello world</h1>;
}
}
// The same component class, written as a stateless functional component:
export const MyComponentClass = () => {
return <h1>Hello world</h1>;
}
You can pass the properties from the container class like this
import React from 'react';
export const GuineaPigs = (props) => {
let src = props.src;
return (
<div>
<h1>Cute Guinea Pigs</h1>
<img src={src} />
</div>
);
}
Prototypes
Add them at the bottom so people can see how to use the code
import React from 'react';
export class BestSeller extends React.Component {
render() {
return (
<li>
Title: <span>
{this.props.title}
</span><br />
Author: <span>
{this.props.author}
</span><br />
Weeks: <span>
{this.props.weeksOnList}
</span>
</li>
);
}
}
// This propTypes object should have
// one property for each expected prop:
BestSeller.propTypes = {
title: React.PropTypes.string.isRequired,
author: React.PropTypes.string.isRequired,
weeksOnList: React.PropTypes.number.isRequired,
};
And a render example
import React from 'react';
import ReactDOM from 'react-dom';
import { BestSeller } from './BestSeller';
export class BookList extends React.Component {
render() {
return (
<div>
<h1>Best Sellers</h1>
<div>
<ol>
<BestSeller
title="Glory and War Stuff for Dads"
author="Sir Eldrich Van Hoorsgaard"
weeksOnList={10} />
<BestSeller
title="The Crime Criminals!"
author="Brenda Sqrentun"
weeksOnList={2} />
<BestSeller
title="Subprime Lending For Punk Rockers"
author="Malcolm McLaren"
weeksOnList={600} />
</ol>
</div>
</div>
);
}
}
ReactDOM.render(<BookList />, document.getElementById('app'));
And for stateless functions
import React from 'react';
export class GuineaPigs extends React.Component {
render() {
let src = this.props.src;
return (
<div>
<h1>Cute Guinea Pigs</h1>
<img src={src} />
</div>
);
}
}
GuineaPigs.propTypes = {
src: React.PropTypes.string.isRequired
};
Form Example where we track the input value
import React from 'react';
import ReactDOM from 'react-dom';
export class Input extends React.Component {
constructor(props) {
super(props);
this.state = { userInput: '' };
this.handleUserInput = this.handleUserInput.bind(this);
}
handleUserInput(e) {
this.setState({userInput: e.target.value});
}
render() {
return (
<div>
<input type="text" onChange={this.handleUserInput} value={this.state.userInput} />
<h1>{this.state.userInput}</h1>
</div>
);
}
}
ReactDOM.render(
<Input />,
document.getElementById('app')
);
The New Way Example using custom hooks
This is the example from the react course where they broke the game into a custom hook
Display Elements
const StarsDisplay = props => (
<>
{utils.range(1, props.count).map(starId => (
<div key={starId} className="star" />
))}
</>
);
const PlayNumber = props => (
<button
className="number"
style={{backgroundColor: colors[props.status]}}
onClick={() => props.onClick(props.number, props.status)}
>
{props.number}
</button>
);
const PlayAgain = props => (
<div className="game-done">
<div
className="message"
style={{ color: props.gameStatus === 'lost' ? 'red' : 'green'}}
>
{props.gameStatus === 'lost' ? 'Game Over' : 'Nice'}
</div>
<button onClick={props.onClick}>Play Again</button>
</div>
);
Custom Hook
Here is the custom hook which abstracts the state and returns what is necessary for the display element of the game
const useGameState = timeLimit => {
const [stars, setStars] = useState(utils.random(1, 9));
const [availableNums, setAvailableNums] = useState(utils.range(1, 9));
const [candidateNums, setCandidateNums] = useState([]);
const [secondsLeft, setSecondsLeft] = useState(10);
useEffect(() => {
if (secondsLeft > 0 && availableNums.length > 0) {
const timerId = setTimeout(() => setSecondsLeft(secondsLeft - 1), 1000);
return () => clearTimeout(timerId);
}
});
const setGameState = (newCandidateNums) => {
if (utils.sum(newCandidateNums) !== stars) {
setCandidateNums(newCandidateNums);
} else {
const newAvailableNums = availableNums.filter(
n => !newCandidateNums.includes(n)
);
setStars(utils.randomSumIn(newAvailableNums, 9));
setAvailableNums(newAvailableNums);
setCandidateNums([]);
}
};
return { stars, availableNums, candidateNums, secondsLeft, setGameState };
};
The Game
const Game = props => {
const {
stars,
availableNums,
candidateNums,
secondsLeft,
setGameState,
} = useGameState();
const candidatesAreWrong = utils.sum(candidateNums) > stars;
const gameStatus = availableNums.length === 0
? 'won'
: secondsLeft === 0 ? 'lost' : 'active'
const numberStatus = number => {
if (!availableNums.includes(number)) {
return 'used';
}
if (candidateNums.includes(number)) {
return candidatesAreWrong ? 'wrong' : 'candidate';
}
return 'available';
};
const onNumberClick = (number, currentStatus) => {
if (currentStatus === 'used' || secondsLeft === 0) {
return;
}
const newCandidateNums =
currentStatus === 'available'
? candidateNums.concat(number)
: candidateNums.filter(cn => cn !== number);
setGameState(newCandidateNums);
};
return (
<div className="game">
<div className="help">
Pick 1 or more numbers that sum to the number of stars
</div>
<div className="body">
<div className="left">
{gameStatus !== 'active' ? (
<PlayAgain onClick={props.startNewGame} gameStatus={gameStatus} />
) : (
<StarsDisplay count={stars} />
)}
</div>
<div className="right">
{utils.range(1, 9).map(number => (
<PlayNumber
key={number}
status={numberStatus(number)}
number={number}
onClick={onNumberClick}
/>
))}
</div>
</div>
<div className="timer">Time Remaining: {secondsLeft}</div>
</div>
);
};
const StarMatch = () => {
const [gameId, setGameId] = useState(1);
return <Game key={gameId} startNewGame={() => setGameId(gameId + 1)}/>;
}
== Utilities ==
// Color Theme
const colors = {
available: 'lightgray',
used: 'lightgreen',
wrong: 'lightcoral',
candidate: 'deepskyblue',
};
// Math science
const utils = {
// Sum an array
sum: arr => arr.reduce((acc, curr) => acc + curr, 0),
// create an array of numbers between min and max (edges included)
range: (min, max) => Array.from({length: max - min + 1}, (_, i) => min + i),
// pick a random number between min and max (edges included)
random: (min, max) => min + Math.floor(Math.random() * (max - min + 1)),
// Given an array of numbers and a max...
// Pick a random sum (< max) from the set of all available sums in arr
randomSumIn: (arr, max) => {
const sets = [[]];
const sums = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 0, len = sets.length; j < len; j++) {
const candidateSet = sets[j].concat(arr[i]);
const candidateSum = utils.sum(candidateSet);
if (candidateSum <= max) {
sets.push(candidateSet);
sums.push(candidateSum);
}
}
}
return sums[utils.random(0, sums.length - 1)];
},
};
ReactDOM.render(<StarMatch />, mountNode);
Type Script
This is another way to valid props
import * as React from "react";
interface Props {
name: string
}
function Greeting(props : Props) {
return (
<h1>Hello {props.name}</h1>
)
}
Lifecycle
A component “mounts” when it renders for the first time. This is when mounting lifecycle methods get called.
There are three mounting lifecycle methods:
- componentWillMount
- render
- componentDidMount
There are five updating lifecycle methods:
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
Some words of wisdom
A React component should use props to store information that can be changed, but can only be changed by a different component.
A React component should use state to store information that the component itself can change.