Skip to content

React - Getting Started ☑️

A JavaScript library for building user interfaces

The Basics

Why React?

A JavaScript library for building user interfaces

alt

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

  1. Components
  2. Like functions
  3. Input: props, state | Output: UI
  4. Reusable and composable
  5. Can manage a private state
  6. Reactive updates
  7. React will react
  8. Takes updates to the browser
  9. Virtual views in memory
  10. Generate HTML using JavaScript
  11. No HTML template language
  12. Tree reconciliation

React Components

Function Component Input: props(immutable) Output: DOM

const MyComponent = (props) => {
  return (
    <domElementOrComponent ... />
  )
}

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(, mountNode);

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

for (let i = 1; i <= 10; i++) {
  // Block Scope
}

i; // ReferenceError: i is not defined

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

const answer = 42;
const greeting = 'Hello';

Scalar value assigned to const is immutable as these values are immutable

Array and Objects

const numbers = [2, 3, 4];
const person = {
  firstName = 'John',
  lastName = 'Doe',
};

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.

const square = a => a * a;

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

const { Component, Fragment, useState } = require('react');
useState();

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

const greeting = 'Hello';
const answer = 'Forty Two';

const html = `<div>${Math.random()}</div>`;

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

Playground

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

CSS libraries for React

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

constructor(props) {
  super(props);
  this.state = {
    profiles: testData
  }
}

to

state = {};

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:

  1. Extracting Components for Reusability and Readability
  2. View Function: State => UI
  3. Behavior Functions: State => New State
  4. Resetting the State
  5. Using Side Effects Hooks
  6. 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);

Using Custom Hooks

Resources