React notes

Main Concepts

  1. Hello World
  2. Introducing JSX
  3. Rendering Elements
  4. Components and Props
  5. State and Lifecycle
  6. Handling Events
  7. Conditional Rendering
  8. Lists and Keys
  9. Forms
  10. Lifting State Up
  11. Composition vs Inheritance
  12. Thinking In React

Hello World

The smallest React example looks like this:

ReactDOM.render(
    <h1>Hello, world!</h1>
    document.getElementById('root')
);

Introducing JSX

JSX is neither a string nor HTML. It is a synax extension to JavaScript.

const element = <div>Hello world!</div>
React.createElement('div', null, 'Hello world!')

Embedding Expressions in JSX

const name = 'Josh Perez'
const element = <h1>Hello, {name}</h1>

ReactDOM.render(element, document.getElementById('root'))
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'))

JSX is an Expression Too

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>
  }
  return <h1>Hello, Stranger.</h1>
}

Specifying Attributes in JSX

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

Specifying Children with JSX

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>

JSX Represents Objects

These two examples are identical:

const element = <h1 className="greeting">Hello, world!</h1>
const element = React.createElement(
  'h1',
  { className: 'greeting' },
  'Hello, world!'
)

React.createElement() essentially creates an object like this:

// Note: this structure is simplified
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!',
  },
}

Rendering Elements

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

Rendering an Element into the DOM

<div id="root"></div>
const element = <h1>Hello, world</h1>
ReactDOM.render(element, document.getElementById('root'))

Updating the Rendered Element

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)

React Only Updates What’s Necessary


Components and Props

Function and Class Components

function component:

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

class component:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

Rendering a Component

a React element that represetns a DOM tag

const element = <div />

a React element representing a user-defined component

const element = <Welcome name="Sara" />
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>
}

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

Note: Always start component names with a capital letter

Composing Components

An App component that is composed of Welcome components

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

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  )
}

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

Extracting Components

Split components into smaller components:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img
          className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">{props.author.name}</div>
      </div>
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</div>
    </div>
  )
}

extract Avatar:

function Avatar(props) {
  return (
    <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />
  )
}

Comment component simplified using Avatar component

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <div className="UserInfo-name">{props.author.name}</div>
      </div>
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</div>
    </div>
  )
}

extract UserInfo component

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">{props.user.name}</div>
    </div>
  )
}

simplify Comment component even further

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</div>
    </div>
  )
}

Props are Read-Only

function sum(a, b) {
  return a + b
}
function withdraw(account, amount) {
  account.total -= amount
}

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


State and Lifecycle

previously, we use ReactDOM.render()

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)

now, we will make the Clock component truly reusable and encapsulated by:

  • converting the function to a class
  • adding local state to the class
  • adding lifecycle methods to the class
class Clock extends React.Component {
  constructor(props) {
    super(props)
    this.state = { date: new Date() }
  }

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

  componentWillUnmount() {
    clearInterval(this.timerID)
  }

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

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

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

Using State Correctly

Do Not Modify State Directoy
// Wrong
this.state.comment = 'Hello'

// Correct
this.setState({ comment: 'Hello' })
State Updates May Be Asynchronous

Do not rely on their values for calculating the next state

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

// Correct (using arrow function)
this.setState((state, props) => ({
  counter: state.counter + props.increment,
}))

// Correct (using function expression)
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment,
  }
})
State Updates are Merged
  // two independent states
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

  // states can be updated independently
  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    // comment is replaced, but posts is left intact
    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }
The Data Flows Down

a component may choose to pass its state down as props to its child

;<h2>It is {this.state.date.toLocaleTimeString()}.</h2>(
  <FormattedDate date={this.state.date} />
)

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

Handling Events

handling events in HTML

<button onclick="activateLasers()">
  Activate Lasers
</button>

handling events in React

<button onClick={activateLasers}>Activate Lasers</button>

Note: In React, you must call preventDefault explicitly to prevent default behavior. You cannot just return false as in html.

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>
function ActionLink() {
  function handleClick(e) {
    e.preventDefault()
    console.log('The link was clicked.')
  }

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

When you define a component using ES6 class, it’s common for an event handler to be a method on 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(state => ({
      isToggleOn: !state.isToggleOn,
    }))
  }

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

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

Note: Be careful with the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. so don’t forget to bind this.handleClick and pass it to onClick. Otherwise, this will be undefined when the function is actually called.

If calling bind annoys you, there are two ways to get around this:

  1. use experimental public class fields syntax
class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this)
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>
  }
}
  1. use arrow function in callback (not recommended. might do an extra re-rendering if this callback is passed as a prop to lower components)
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this)
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return <button onClick={e => this.handleClick(e)}>Click me</button>
  }
}

Note: We recommend binding in the constructor or using the class fields syntax, to avoid this sort of performance problem.

Passing Arguments to Event Handlers

// using arrow function
;<button onClick={e => this.deleteRow(id, e)}>Delete Row</button>(
  // use Function.prototype.bind
  <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
)

Conditional Rendering

Consider these two components:

function UserGreeting(props) {
  return <h1>Welcome back!</h1>
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>
}

A Greeting component that conditionally displays one of the above components:

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

use variables to store elements. conditionally render a part of the component while the rest of the output doesn’t change.

consider these two components:

function LoginButton(props) {
  return <button onClick={props.onClick}>Login</button>
}

function LogoutButton(props) {
  return <button onClick={props.onClick}>Logout</button>
}

a stateful component that conditionally renders LoginButton or LogoutButton depending on its current state.

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

    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>
      // conditionally include an element
      {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')
)

Inline If with Logical && Operator

conditionally render a small block of text

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

Preventing Components from Rendering

return null to prevent component from being rendered

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(state => ({
      showWarning: !state.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'))

Note: returning null from a component’s render method doesn’t affect the component’s lifecycle methods. For instance, componentDidUpdate will still be called.


List and Keys

transform lists in JavaScript

const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(number => number * 2)
console.log(doubled)

Rendering Multiple Components

build collections of elements and include them in JSX using curly braces {}

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

refactor previous example into a component that accepts an arrao fo numbers and outputs a list of elements

// warning: a key should be provided for list items
function NumberList(props) {
  const numbers = props.numbers
  const listItems = numbers.map(number => <li>{number}</li>)
  return <ul>{listItems}</ul>
}

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

assign a key to our list items inside numbers.map() and fix the missing key issue

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 been changed, added, or removed. don’t forget to five keys to elements inside the array.

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

pick a key by using a string that uniquely identifies a list item

const todoItems = todos.map(todo => <li key={todo.id}>{todo.text}</li>)

if you don’t have stable IDs, use item index as a key

const todoItems = todos.map((todo, index) => (
  // Only do this if items have no stable IDs
  <li key={index}>{todo.text}</li>
))

Note: don’t use indexes for keys if the order of the items might change

Extracitng Components with Keys

Example: Incorrect Key Usage

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')
)

Example: Correct Key Usage

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

function NumberList(props) {
  const numbers = props.numbers
  const listItems = numbers.map(number => (
    // Correct! Key should be specified inside the array.
    <ListItem key={number.toString()} 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

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
  const content = props.posts.map(post => (
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  ))
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  )
}

const posts = [
  { id: 1, title: 'Hello World', content: 'Welcome to learning React!' },
  { id: 2, title: 'Installation', content: 'You can install React from npm.' },
]
ReactDOM.render(<Blog posts={posts} />, document.getElementById('root'))

Keys don’t get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name:

const content = posts.map(post => (
  <Post key={post.id} id={post.id} title={post.title} />
))

Embedding map() in JSX

In the examples above, we declared a separate ListItems variable and included it in JSX:

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

JSX allows embedding any expression in curly braces so we could inline the map() result

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

Forms

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</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>
    )
  }
}

Example of modifying/validating user input to enforce names written in all uppercase letter

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

The textarea tag

<textarea>
  Hello there, this is some text in a text area
</textarea>

React, <textarea> uses value attribute

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>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    )
  }
}

The select tag

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

In React

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 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>
    )
  }
}

Note: You can pass an array into the value attribute, allowing you to select multiple options in a select tag:

<select multiple={true} value={['B', 'C']}>

The file input Tag

<input type="file" />

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>
    )
  }
}

we used the ES6 computed property name to update the state

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

it is equivalent to this ES5 code

var partialState = {}
partialState[name] = value
this.setState(partialState)

Controlled Input Null Value

ReactDOM.render(<input value="hi" />, mountNode)

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

Alternatives to Controlled Components

You can use uncontrolled components as an alternative:

  • when working with non-React libraries
  • when converting a preexisting codebase to React
  • when you need to directly interact with the DOM

Fully-Fledged Solutions

Formik is a popular solution

  • validation
  • keep track of visited fields
  • handling form submission

Lifting State Up

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>
  }
  return <p>The water would not boil.</p>
}
class Calculator extends React.Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
    this.state = { temperature: '' }
  }

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

  render() {
    const temperature = this.state.temperature
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input value={temperature} onChange={this.handleChange} />

        <BoilingVerdict celsius={parseFloat(temperature)} />
      </fieldset>
    )
  }
}

Adding a Second Input

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

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

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

  render() {
    const temperature = this.state.temperature
    const scale = this.props.scale
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>
    )
  }
}
class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    )
  }
}

Writing Conversion Functions

function toCelsius(fahrenheit) {
  return ((fahrenheit - 32) * 5) / 9
}

function toFahrenheit(celsius) {
  return (celsius * 9) / 5 + 32
}
function tryConvert(temperature, convert) {
  const input = parseFloat(temperature)
  if (Number.isNaN(input)) {
    return ''
  }
  const output = convert(input)
  const rounded = Math.round(output * 1000) / 1000
  return rounded.toString()
}

Lifting State Up

Currently TemperatureInput components keep local state:

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

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

  render() {
    const temperature = this.state.temperature;
    // ...

Let’s remove this state from TemperatureInput component:

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

  handleChange(e) {
    // Before: this.setState({temperature: e.target.value});
    this.props.onTemperatureChange(e.target.value)
  }

  render() {
    // Before: const temperature = this.state.temperature;
    const temperature = this.props.temperature
    const scale = this.props.scale
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>
    )
  }
}

Remember: props are read-only

And let’s move the state up to the Calculator component

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

  handleCelsiusChange(temperature) {
    this.setState({ scale: 'c', temperature }) // set state
  }

  handleFahrenheitChange(temperature) {
    this.setState({ scale: 'f', temperature }) // set state
  }

  render() {
    // set props from state
    const scale = this.state.scale
    const temperature = this.state.temperature
    const celsius =
      scale === 'f' ? tryConvert(temperature, toCelsius) : temperature
    const fahrenheit =
      scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange}
        />

        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange}
        />

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

Lessons Learned

There should be a single “source of truth” for any data that changes in a React application.

If something can be derived from either props or sttate it probably shouldn’t be in the state.


Source

© 2020 | Paul Kim