React
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
Variables
const myVar = (
<h1>Love it</h1>
);
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.