React - Getting Started ☑️¶
A JavaScript library for building user interfaces
The Basics¶
Why React?¶
A JavaScript library for building user interfaces
HTML is also declarative but React is declarative for dynamic data
A "language" to model the state of UIs, not the transactions on them.
- The "virtual" browser (vs. DOM API)
- Just JavaScript
- React Native (for the win)
- Battle-tested
- Declarative language (model UI and state)
React's Basic Concepts¶
- Components
- Like functions
- Input: props, state | Output: UI
- Reusable and composable
- Can manage a private state
- Reactive updates
- React will react
- Takes updates to the browser
- Virtual views in memory
- Generate HTML using JavaScript
- No HTML template language
- Tree reconciliation
React Components¶
Function Component Input: props(immutable) Output: DOM
Class Component Input: State Output: DOM
class MyComponent extends React.Component {
render() {
return (
<domEelementOrComponent ... />
)
}
}
JSX is NOT HTML
```tsx{4-6} class Hello extends React.Component { render() { return (
Getting Started
ReactDOM.render(
The code gets compiled to pure JavaScript as close as to:
```tsx{4-8}
class Hello extends React.Component {
render() {
// return (...)
return React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Getting Started')
);
}
}
ReactDOM.render(React.createElement(Hello, null), mountNode);
First React Component¶
function Hello() {
return <div>Hello React!</div>; // JSX
}
ReactDOM.render(<Hello />, document.getElementById('mountNode'));
Babel converts JSX to React component API. The equivalent react syntax would be
function Hello() {
return React.createElement('div', null, 'Hello React!');
}
ReactDOM.render(
React.createElement(Hello, null),
document.getElementById('mountNode')
);
But it's harder to write the code this way.
Always Capitalize fist letter of a function otherwise react will take that as a html element.
First React Hook¶
function Button() {
const [state, setState] = useState(0);
return <button onClick={() => setCounter(state + 1)}>{state}</button>;
}
ReactDOM.render(<Button />, document.getElementById('mountNode'));
First One-way Data Flow¶
Use `React.Framgment` (or simply `<></>`) to display multiple component or enclose by a parent div (`<div><component1 /><component2 /> <div/>`).
function Button(props) {
return <button onClick={props.onClickFunction}>+1</button>;
}
function Display(props) {
return <div>{props.message}</div>;
}
function App() {
const [counter, setCounter] = useState(0);
const incrementCounter = () => setCounter(counter + 1);
return (
<div>
<Button onClickFunction={incrementCounter} />
<Display message={counter} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('mountNode'));
- Have pure or functional component as much as possible
- Follow single responsibility rule
Components Reusability¶
function Button(props) {
const handleClick = () => props.onClickFunction(props.increment);
return <button onClick={handleClick}>+{props.increment}</button>;
}
function Display(props) {
return <div>{props.message}</div>;
}
function App() {
const [counter, setCounter] = useState(0);
const incrementCounter = incrementValue =>
setCounter(counter + incrementValue);
return (
<div>
<Button onClickFunction={incrementCounter} increment={1} />
<Button onClickFunction={incrementCounter} increment={2} />
<Button onClickFunction={incrementCounter} increment={3} />
<Display message={counter} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('mountNode'));
Note:¶
- if we increment="1" instead of increment={1} we are passing string value
- if we do `return <button onClick={props.onClickFunction(props.increment)}>+{props.increment}</button>` we need the function reference not invocation of a function so it should be `return <button onClick={() => props.onClickFunction(props.increment)}>+{props.increment}</button>`
Tree Reconciliation in Action¶
const render = () => {
document.getElementById('mountNode').innerHTML = `
<div>
Hello HTML
<input />
<pre>${new Date().toLocaleTimeString()}</pre>
</div>
`;
ReactDOM.render(
<div>
Hello HTML
<input />
<pre>{new Date().toLocaleTimeString()}</pre>
</div>,
document.getElementById('mountNode2')
);
};
setInterval(render, 1000);
The first component would reference every second and we won't be able to type in the input. But the second react component would just change the value of the time. This why it makes sense to use react over the HTML.
Modern JavaScript Crash Course¶
ECMAScript and TC39¶
Variables and Block Scopes¶
Block Scope¶
A block scope is the area within if, switch conditions or for and while loops. Generally speaking, whenever you see {curly brackets}, it is a block. In ES6, const and let keywords allow developers to declare variables in the block scope, which means those variables exist only within the corresponding block.
{
{
{
var v = 42;
}
}
}
v; // 42
if (true) {
// Block Scope
}
for (var i = 1; i <= 10; i++) {
// Block Scope
}
i; // 11
To fix block issue
Function Scope¶
Function scope is different than block scope and variable defined inside function is not accessible out the function.
function sum(a, b) {
// Function Scope
var result = a + b;
}
sum(4, 3);
result; //ReferenceError: result is not defined
Const¶
Scalar Values
Scalar value assigned to const is immutable as these values are immutable
Array and Objects
References assigned to const doesn't make the object immutable, we can still change the value of the object like person and numbers.
Const vs Let¶
const answer = 42;
// Big problem here.
answer; // is still 42
let answer1 = 42;
// Big problem here
answer1; // Might have changed
Arrow Functions¶
Regular functions give access to their calling
environment while arrow functions give access to their defining
environment
The value of the this
keyword inside a regular function depends on HOW the function was CALLED (the object that made the call)
The value of the 'this' keyword inside an arrow function depends on WHERE the function was DEFINED (the SCOPE that defined the function)
const X = function name(params) {
// 'this' here is the called of X
};
const Y = () => {
// 'this' here is NOT the called of Y
// It's the same 'this' found in Y's scope
};
console.log(this); // {id: "PLAYGROUND"}
const testerObj = {
func1: function() {
console.log('func1', this);
},
func2: () => {
console.log('func2', this);
},
};
testerObj.func1(); // {func1: ƒ, func2: ƒ} which is testerObj
testerObj.func2(); // {id: "PLAYGROUND"}
Arrow function is very beneficial for listeners and event handlers.
Short notation if the function is single line.
Very popular syntax for map or functional programming.
Object Literals¶
const mystery = 'answer';
const InverseOfPI = 1 / Math.PI;
const obj = {
p1: 10,
p2: 20,
f1() {},
f2: () => {}
[mystery] = 42; // Dynamics property,
InverseOfPI
}
console.log(obj.answer); // 42
Destructuring and Rest/Spread¶
const PI = Math.PI;
const E = Math.E;
const SQRT2 = Math.SQRT2;
// With destructuring
const { PI, E, SQRT2 } = Math;
We use similar syntax while importing in React
Also works in function arguments which we use in props.
const circle = {
label: 'circleX',
radius: 2,
};
const circleArea = ({ radius }) => (PI * radius * radius).toFixed(2);
console.log(
circleArea(circle);
)
We can use destructuring with defaults function arguments as well
const circle = {
label: 'circleX',
radius: 2,
};
const circleArea = ({ radius }, {precision = 2} = {}) => (PI * radius * radius).toFixed(precision);
console.log(
circleArea(circle, {precision: 5});
)
Destructuring with Arrays
const [first, second, , fourth] = [10, 20, 30, 40];
// In React
const [state, setState] = useState(initialState); // Since useState return array with two items in it
const [first, ...restOfItems] = [10, 20, 30, 40];
So, it's very useful to splitting the array. Also useful to filter the object properties
const data = {
temp1: '001',
temp2: '002',
firstName: 'John',
lastName: 'Doe',
};
const { temp1, temp2, ...person } = data;
const newArray = [...restOfItems]; // Shallow copy
const newObject = {
// Shallow copy
...person,
};
In shallow copy, the nested objects will be the shared.
Template Strings¶
Classes¶
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}`);
}
}
class Student extends Person {
constructor(name, level) {
super(name);
this.level = level;
}
greet() {
console.log(`Hello ${this.name} from ${this.level}`);
}
}
const o1 = new Person('Max');
const o2 = new Student('Tina', '1st Grade');
const o3 = new Student('Mary', '2st Grade');
o3.greet = () => console.log('I am special');
o1.greet(); // Hello Max
o2.greet(); // Hello Tina from 1st Grade
o3.greet(); // I am special
Promises and Async/Await¶
Calls using promises
const fetchData = () => {
fetch('https://api.github.com').then(resp => {
resp.json().then(data => {
console.log(data);
});
});
};
fetchData();
Modern and readable way is to use async await
const fetchData = async () => {
const resp = await fetch('https://api.github.com');
const data = await resp.json();
console.log(data);
};
fetchData();
The GitHub Cards App¶
React Class Components¶
We usually use functional component with hook nowadays as in "The Star Match Game" but we still need to know about the class components
- Props and State are both managed inside the class
Styling React Components¶
- We can use global CSS class or use react style property `style={{ marginLeft: '1rem' }}` which is object literal inside JSX expression and it's javascript with camel case
- Mix of global CSS and react style for conditional styling
Working with Data¶
`{testData.map(profile => <Card {...profile}/>)}` is equivalent to
`[<Card />, <Card />, <Card />]` or
`[React.createElement(), React.createElement(), React.createElement()]`
const testData = [
{
name: 'Dan Abramov',
avatar_url: 'https://avatars0.githubusercontent.com/u/810438?v=4',
company: '@facebook',
},
{
name: 'Sophie Alpert',
avatar_url: 'https://avatars2.githubusercontent.com/u/6820?v=4',
company: 'Humu',
},
{
name: 'Sebastian Markbåge',
avatar_url: 'https://avatars2.githubusercontent.com/u/63648?v=4',
company: 'Facebook',
},
];
const CardList = props => (
<div>
{testData.map(profile => (
<Card {...profile} />
))}
</div>
);
class Card extends React.Component {
render() {
const profile = this.props;
return (
<div className="github-profile">
<img src={profile.avatar_url} />
<div className="info">
<div className="name">{profile.name}</div>
<div className="company">{profile.company}</div>
</div>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<div className="header">{this.props.title}</div>
<CardList />
</div>
);
}
}
ReactDOM.render(<App title="The GitHub Cards App" />, mountNode);
Initializing an Reading the State Object¶
state can be used directly class component without constructor
to
Taking Input from the User¶
How do we get the value from the input
event is the wrapper around the JavaScript event so we need 'event.preventDefault();' to prevent from default submit and reload of the page
Using ref component from the DOM¶
class Form extends React.Component {
userNameInput = React.createRef();
handleSubmit = event => {
event.preventDefault();
console.log(this.userNameInput.current.value);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="GitHub username"
ref={this.userNameInput}
required
/>
<button>Add card</button>
</form>
);
}
}
Controlled component - Control the value through React itself¶
- Define the state object with element to handle input value of the field
- set the `value=` from state
- define `onChange` otherwise we won't be able to type in the input as React is controlling the input
class Form extends React.Component {
state = { userName: '' };
handleSubmit = event => {
event.preventDefault();
console.log(this.state.userName);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.userName}
onChange={event => this.setState({ userName: event.target.value })}
placeholder="GitHub username"
required
/>
<button>Add card</button>
</form>
);
}
}
Working with Ajax Calls¶
We can use fetch method available in the browser or axios library
// GitHub usernames: gaearon, sophiebits, sebmarkbage, bvaughn
const CardList = props => (
<div>
{props.profiles.map(profile => (
<Card key={profile.id} {...profile} />
// unique key is required otherwise react will complain
))}
</div>
);
class Card extends React.Component {
render() {
const profile = this.props;
return (
<div className="github-profile">
<img src={profile.avatar_url} />
<div className="info">
<div className="name">{profile.name}</div>
<div className="company">{profile.company}</div>
</div>
</div>
);
}
}
class Form extends React.Component {
state = { userName: '' };
handleSubmit = async event => {
event.preventDefault();
const resp = await axios.get(
`https://api.github.com/users/${this.state.userName}`
);
this.props.onSubmit(resp.data);
// Clear input after fetch is complete
this.setState({ userName: '' });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.userName}
onChange={event => this.setState({ userName: event.target.value })}
placeholder="GitHub username"
required
/>
<button>Add card</button>
</form>
);
}
}
class App extends React.Component {
state = {
profiles: [],
};
addNewProfile = profileData => {
this.setState(prevState => ({
profiles: [...prevState.profiles, profileData],
}));
};
render() {
return (
<div>
<div className="header">{this.props.title}</div>
<Form onSubmit={this.addNewProfile} />
<CardList profiles={this.state.profiles} />
</div>
);
}
}
ReactDOM.render(<App title="The GitHub Cards App" />, mountNode);
The implementation is just happy path so ask for error handling:
- Invalid Input
- Network Problems
The Star Match Game¶
- Avoid for/while in React and rather use map/filter/reduce
- Make thinks dynamic
- Extract components
- Extract component whenever items that share similar DATA and BEHAVIOR
- Do not override built-in variables so name component with two words
- Component extraction should be balanced and don't overdo it
- Define all the possible minimum state to represent
General steps:
- Extracting Components for Reusability and Readability
- View Function: State => UI
- Behavior Functions: State => New State
- Resetting the State
- Using Side Effects Hooks
- Unmounting and Remounting Components
// STAR MATCH - V9
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>
);
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 };
};
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)} />;
};
// 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);