Build a user interface with React

ECA React - December 5th 2018

Last time...

  • Render classes dynamically

  • Render lists

  • Conditional rendering

  • Handling events

  • Composing components

  • Passing data to components

  • State vs props

Let's continue !

Passing data to components

  • Data can be passed through attributes

  • <PokeCounter key={counter.id} count={counter.count} anotherAttribute="a value I pass to my component"/>
  • You can then retrieve the data from a plain JavaScript object called props

  • key will not be passed as it is a special keyword used to identify elements in a list

pokeCounter.jsx

import React, { Component } from "react";
import PokeCounter from "./pokeCounter";

class PokeCounters extends Component {
  state = {
    counters: [
        { id: 1, value: 0 }
        { id: 2, value: 0 },
        { id: 3, value: 0 },
        { id: 4, value: 0 },
        { id: 5, value: 0 }
    ]
  };

  render() {
    return (
        <div>
            {this.state.counters.map(counter => 
                <PokeCounter 
                    key={counter.id} 
                    value={counter.value}
                />
            )}
        </div>
    );
  }
}

export default PokeCounters;
import React, { Component } from "react";

class PokeCounter extends Component {
  state = {
    value: this.props.value
  };

  /** [...] */

  incrementCount = () => {
    this.setState({ 
        value: this.state.value + 1 
    });
  };

  render() {
    return (
      <React.Fragment>
        <span 
            className={this.getBadgesClasses()}>
            {this.formatCount()}
        </span>
        <button 
            onClick={this.incrementCount} 
            className="btn btn-secondary btn-sm"
        >
        Increment
        </button>
      </React.Fragment>
    );
  }
}

export default PokeCounter;

pokeCounters.jsx

  • In case of a complex element, you can pass data as children props

  • <PokeCounter key={counter.id} count={counter.count}>
        <h1>Hello world</h1>
    </PokeCounter>
  • data is then accessible in props.children

React dev tools

Available in Chrome and Firefox

Raising and handling events

2

Increment

3

5

0

0

Delete

Increment

Delete

Increment

Delete

Increment

Delete

Increment

Delete

Let's add "Delete" button

PokeCounters

PokeCounter

state = {

  counters: []

};

Delete button

How to update that state?

The component that owns a piece of state should be the one modifying it

PokeCounters

PokeCounter

handleDelete

onDelete

class PokeCounters extends Component {
  /** [...] */

  handleDelete = () => {
    console.log("Event handler called");
  };

  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <PokeCounter
            key={counter.id}
            value={counter.value}
            onDelete={this.handleDelete}
          />
        ))}
      </div>
    );
  }
}
class PokeCounter extends Component {
  
  /** [...] */
  render() {
    return (
      <div>
        /** [...] */
        <button
          className="btn btn-danger btn-sm-m-2"
          onClick={this.props.onDelete}
        >
          Delete
        </button>
      </div>
    );
  }
}

Update the state

class PokeCounters extends Component {
  
  /** [...] */

  handleDelete = counterId => {
    const counters = this.state.counters.filter(c => c.id !== counterId);
    this.setState({ counters });
  };

  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <PokeCounter
            key={counter.id}
            value={counter.value}
            id={counter.id}
            onDelete={this.handleDelete}
          />
        ))}
      </div>
    );
  }
}
class PokeCounter extends Component {
  
  /** [...] */
  render() {
    return (
      <div>
        /** [...] */
        <button
          className="btn btn-danger btn-sm-m-2"
          onClick={() => this.props.onDelete(this.props.id)}
        >
          Delete
        </button>
      </div>
    );
  }
}
class PokeCounters extends Component {
  /** ... */

  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <PokeCounter
            key={counter.id}
            counter={counter}
            onDelete={this.handleDelete}
          />
        ))}
      </div>
    );
  }
}
class PokeCounter extends Component {
  state = {
    value: this.props.counter.value
  };

  /** ... */

  render() {
    return (
      <div>
        /** ... */
        <button
          className="btn btn-danger btn-sm-m-2"
          onClick={() => this.props.onDelete(this.props.counter.id)}
        >
          Delete
        </button>
      </div>
    );
  }
}

Single Source of Truth

2

Increment

3

5

0

0

Delete

Increment

Delete

Increment

Delete

Increment

Delete

Increment

Delete

Reset

Let's add "Reset" button

class PokeCounters extends Component {
  /** [...] */

  handleReset = () => {
    const counters = this.state.counters.map(c => {
      c.value = 0;
      return c;
    });
    this.setState({ counters });
  };

  render() {
    return (
      <div>
        <button
          className="btn btn-primary btn-sm-m-2"
          onClick={this.handleReset}
        >
          Reset
        </button>
        /** [...] */
        </div>
      </div>
    );
  }
}

It does not work because PokeCounter has a local state

Removing the local state

PokeCounters

PokeCounter

Controlled component

data (props)

raise events

import React, { Component } from "react";

class PokeCounter extends Component {
  formatCount() {
    return this.props.counter.value === 0 ? "Zero" : this.props.counter.value;
  }

  getBadgesClasses() {
    let classes = "badge m-2 badge-";
    classes += this.props.counter.value === 0 ? "warning" : "primary";
    return classes;
  }

  render() {
    return (
      <div>
        <span className={this.getBadgesClasses()}>{this.formatCount()}</span>
        <button
          onClick={() => this.props.onIncrement(this.props.counter)}
          className="btn btn-secondary btn-sm m-2"
        >
          Increment
        </button>
        <button
          className="btn btn-danger btn-sm-m-2"
          onClick={() => this.props.onDelete(this.props.counter.id)}
        >
          Delete
        </button>
      </div>
    );
  }
}

export default PokeCounter;
import React, { Component } from "react";
import PokeCounter from "./pokeCounter";

class PokeCounters extends Component {
  state = {
    counters: [
      { id: 1, value: 1 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
      { id: 4, value: 0 },
      { id: 5, value: 0 }
    ]
  };

  handleDelete = counterId => {
    const counters = this.state.counters.filter(c => c.id !== counterId);
    this.setState({ counters });
  };

  handleIncrement = counter => {
    const counters = [...this.state.counters];
    const index = counters.indexOf(counter);
    counters[index] = { ...counter };
    counters[index].value++;
    this.setState({ counters });
  };

  handleReset = () => {
    const counters = this.state.counters.map(c => {
      c.value = 0;
      return c;
    });
    this.setState({ counters });
  };

  render() {
    return (
      <div>
        <button
          className="btn btn-primary btn-sm-m-2"
          onClick={this.handleReset}
        >
          Reset
        </button>
        <div>
          {this.state.counters.map(counter => (
            <PokeCounter
              key={counter.id}
              counter={counter}
              onDelete={this.handleDelete}
              onIncrement={this.handleIncrement}
            />
          ))}
        </div>
      </div>
    );
  }
}

export default PokeCounters;

Multiple components in sync

App

NavBar

PokeCounters

PokeCounter

counters[]

Stateless functional component

See you next wednesday!

2

+

-

3

+

-

5

+

-

0

+

-

0

+

-

Total

10

Reset

Delete

Delete

Delete

Delete

Delete

Build a user interface with React (3/5)

By Issam Hammi

Build a user interface with React (3/5)

ECA React - December 5th 2018

  • 587