Skip to content

Instantly share code, notes, and snippets.

@idrakimuhamad
Last active October 12, 2017 03:54
Show Gist options
  • Save idrakimuhamad/7d0026110a39b0c68feb3dbab8404932 to your computer and use it in GitHub Desktop.
Save idrakimuhamad/7d0026110a39b0c68feb3dbab8404932 to your computer and use it in GitHub Desktop.
Basic React Introduction

Introduction to React

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);
  • Declarative
  • Component-Based
  • Learn Once, Write Anywhere

Simple Component

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="John" />,
  mountNode
);

Stateful Component

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }

  tick() {
    this.setState((prevState) => ({
      seconds: prevState.seconds + 1
    }));
  }

  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (
      <div>
        Seconds: {this.state.seconds}
      </div>
    );
  }
}

ReactDOM.render(<Timer />, mountNode);

JSX

const element = <h1>Hello, world!</h1>;

This is called JSX, a syntax extension to JavaScript. JSX produces React “elements”

Embedding Expressions in JSX

You can embed any JavaScript expression in JSX by wrapping it in curly braces.

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
  Hello, {formatName(user)}!
  </h1>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

After compilation, JSX expressions become regular JavaScript objects.

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}
Specifying Attributes with JSX

You may use quotes to specify string literals as attributes. You may also use curly braces to embed a JavaScript expression in an attribute

const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

Don’t put quotes around curly braces when embedding a JavaScript expression in an attribute. You should either use quotes (for string values) or curly braces (for expressions), but not both in the same attribute.

Since JSX is closer to JavaScript than HTML, React DOM uses camelCase property naming convention instead of HTML attribute names.

Specifying Children with JSX

If a tag is empty, you may close it immediately with />, like XML

const element = <img src={user.avatarUrl} />;

JSX also may contained children

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);
JSX Prevents Injection Attacks
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;

By default, React DOM escapes any values embedded in JSX before rendering them. Thus it ensures that you can never inject anything that’s not explicitly written in your application. Everything is converted to a string before being rendered. This helps prevent XSS (cross-site-scripting) attacks.

JSX Represents Objects
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() performs a few checks to help you write bug-free

Rendering element

const element = <h1>Hello, world</h1>;

Unlike browser DOM elements, React elements are plain objects, and are cheap to create. React DOM takes care of updating the DOM to match the React elements.

Rendering an Element into the DOM
const element = <h1>Hello, world</h1>;
ReactDOM.render(
  element,
  document.getElementById('root')
);
Updating the Rendered Element

React elements are immutable.

React Only Updates What’s Necessary

React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring the DOM to the desired state

Components and Props

Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.

Functional and Class Components

Functional:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

Class:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
Rendering a Component
const element = <Welcome name="Sara" />;

When React sees an element representing a user-defined component, it passes JSX attributes to this component as a single object. We call this object “props”.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);
Composing Components

Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail

Extracting Components

Split components into smaller components.

Props are Read-Only

Whether you declare a component as a function or a class, it must never modify its own props.

function sum(a, b) {
  return a + b;
}

All React components must act like pure functions with respect to their props.

State and Lifecycle

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);
Converting a function into a Class
import { Component } from 'react';

class Clock extends Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

We can't/mustn't change the props

Adding Local State to a Class
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Add class constructor. A constructor is a special method for creating and initializing an object created with a class.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

A react class should always call the base constructor with props.

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);
Lifecycle Methods

We want to set up a timer whenever the Clock is rendered to the DOM for the first time. This is called “mounting” in React. We also need make sure that we clear the timer when the DOM element of the Clock was removed, or called "unmounting".

React have various lifecycle hooks, but for this case we going to use 2 hooks.

componentDidMount: runs after the component has been mounted, or rendered to the DOM. Its the best place to run any 3rd party plugin initializing that requiring for DOM to be ready

componentWillUnmount: runs when a component was unmounted, or when the DOM rendered are removed, for example when navigating from one page to another

Best place to run the timer is in the componentDidMount

componentDidMount() {
  this.timerID = setInterval(
    () => this.tick(),
    1000
  );
}

And best place to clear the timer is in componentWillUnmount

componentWillUnmount() {
  clearInterval(this.timerID);
}

We can then use setState to mutate the clock value whenever the timer was called

tick() {
  this.setState({
    date: new Date()
  });
}

Rule of using State

Do Not Modify State Directly

// Wrong
this.state.comment = 'Hello';

// Correct
this.setState({comment: 'Hello'});

State maybe async

Multiple setState() call maybe call in batch for Performance

// Wrong (maybe)
this.setState({
  counter: this.state.counter + this.props.increment,
});

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

Update Are Merged

React (shallow) merges the object you provide into the current state.

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}
componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}

Data Flow

State should be encapsulated or local to the component that own and sets it

<FormattedDate date={this.state.date} />

Within the component, it can access this state from the parents through their props, and it wouldn't know where does it came from or whether it came as a props or a state of the parents. They shouldn't care about it.

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

A stateless component should have one job and do that job well. In most cases is to render it regardless where it came from.

This flow known commonly as unidirectional or top-down data flow. You can imagine a component tree like a waterfall. This allow all the component to be truly isolated and each of them will run independently

Events

Reacts event are name in CamelCase.

<button onClick={launchNuclear}>
  Trump Button
</button>

You can't use return false, the traditional way to prevent default browser behavior. It must be done explicitly using the preventDefault method.

// HTML way
<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

// react way
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

event returned from the handler is a synthetic event, which is a cross-browser wrapper around the browser’s native event

You don't need to call addEventListener in React, instead, you just assign the event handler.

When defining component as a react class, it is common to create the event handler as a method within the class.

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
Argument to Handlers
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

Conditional Rendering

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);
Element Variables
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;

    let button = null;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);
Inline if with Logical Operator
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

true && expression = expression false && expression = false

Inline if else with Conditional Operator
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

Can also be use to render component conditionally

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}
Preventing Component from Rendering
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true}
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(prevState => ({
      showWarning: !prevState.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

Returning null within the render() method will not affect the lifecycle method -- it will still be triggered.

Map, Lists & Keys

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);
Basic List Component
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
Keys

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity

Extracting Components with Keys

Keys only make sense in the context of the surrounding array

function ListItem(props) {
  const value = props.value;
  return (
    // Wrong! There is no need to specify the key here:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Wrong! The key should have been specified here:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
Keys Must Only Be Unique Among Siblings

Keys used within arrays should be unique among their siblings.

However they don’t need to be globally unique

Embedding map() in JSX
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />

      )}
    </ul>
  );
}

Forms

It’s convenient to have a JavaScript function that handles the submission of the form and has access to the data that the user entered into the form

Controlled Components
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
Textarea
class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: 'Please write an essay about your favorite DOM element.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
Select
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

For multiple selection, use the multiple attribute and pass an array of value

<select multiple={true} value={['B', 'C']}>
Handling Multiple Inputs
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
Controlled Input Null Value
ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

Lifting State Up

Sometimes, you may need to have several components to reflect the same changing data. This can be done by lifting the shared state up to their closest common ancestor.

function BoilingVerdict(props) {
  if (props.celcius >= 100) {
    return <p>The water would boil</p>
  }

  return <p>The water would not boil</p>
}

class Calculator extends Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
    this.state = {
      temperature: ''
    }
  }

  handleChange(e) {
    this.setState({
      temperature: e.target.value
    })
  }

  render() {
    const temp = this.state.temperature

    return (
      <fieldset>
        <legend>Enter temperature in celcius:</legend>
        <input
          value={temp}
          onChange={this.handleChange} />

        <BoilingVerdict
          celcius={parseFloat(temp)} />
      </fieldset>
    )
  }
}

Assume that we also want to provide a Fahrenheit input and also making sure that both of the input sync

const scaleNames = {
  c: 'Celcius',
  f: 'Fahrenheit'
}

class TemperatureInput extends Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
    this.state = {
      temperature: ''
    }
  }

  handleChange(e) {
    this.setState({
      temperature: e.target.value
    })
  }

  render() {
    const temp = this.state.temperature
    const scale = this.props.scale

    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input
          value={temp}
          onChange={this.handleChange} />
      </fieldset>
    )
  }
}

class Calculator extends Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />            
      )
  }
}

BoilingVerdict can't display the verdict because the Calculator component doesn't have the information about the temperature, now that it live inside TemperatureInput component.

function toCelcius(f) {
  return (f - 32) * 5 / 9
}

function toFahrenheit(c) {
  return (c * 9 / 5) + 32
}

function conversion(temp, convert) {
  const input = parseFloat(temp)

  if (Number.isNaN(input)) {
    return ''
  }

  const output = convert(input)
  const rounded = Math.round(output * 1000) / 1000

  return rounded.toString()
}
Keeping the state in sync

We can accomplished this by moving the state to their common ancestor, which is known as lifting state up

class TemperatureInput extends Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value)
  }

  render() {
    const temp = this.props.temperature
    const scale = this.props.scale

    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input
          value={temp}
          onChange={this.handleChange} />
      </fieldset>
    )
  }
}

The state should be moved to the Calculator component so it will be a single source of truth

class Calculator extends Component {
  constructor(props) {
    super(props)
    this.handleCelciusChange = this.handleCelciusChange.bind(this)
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this)
    this.state = {
      temperature: '',
      scale: 'c'
    }
  }

  handleCelciusChange(e) {
    this.setState({
      scale: 'c'
      temperature: e.target.value
    })
  }

  handleFahrenheitChange(e) {
    this.setState({
      scale: 'f'
      temperature: e.target.value
    })
  }

  render() {
    const { scale, temperature } = this.state
    const celcius = scale === 'f' ? conversion(temperature, toCelcius) : temperature
    const fahrenheit = scale === 'c' ? conversion(temperature, toFahrenheit) : temperature

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celcius}
          onTemperatureChange={this.handleCelciusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />

        <BoilingVerdict
          celcius={parseFloat(celcius)} />
      </div>
    )
  }
}

Lesson Learned

There should be a single "source of truth" for any data changes in a react application

Remember: top-down data flow

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment