← Back

My notes on React For Beginners by Wes Bos

The Shadow DOM

React doesn't update all elements on the screen when there is a change but it only rerenders the elements that have changed. This is part of what makes React feel fast (after initial load). React does this with the help of the "Shadow DOM". This is basically a copy of the actual DOM. They apply the changes to the Shadow DOM and then compare this to the actual DOM to see what elements have changed and what elements therefor have to be rerendered.

Basic component template

import React from 'react';

class ComponentName extends React.Component {
  render() {
    return (
      <p>JSX HTML</p>
    )
  }
}

export default ComponentName;

Importing React parts

import React from 'react';

This will require you to concatenate everything with React.Whatever. Like React.Component or React.Fragment.

import React, { Component, Fragment } from 'react';

With this import you don't have to concatenate Component and Fragment with React..

class ComponentName extends Component {
  render() {
    return (
      <Fragment>
      </Fragment>
    )
  }
}

React.Fragment

Similar to ng-container in Angular. Compiles to nothing, meaning it will not show up in the HTML. Inside the render() method you can only return a single item. No siblings. So all JSX (HTML) that you return will have to be wrapped in a single other element. React.Fragment can be used in this case.

render() {
  return (
    <React.Fragment>
      <p>Sibling 1</p>
      <p>Sibling 2</p>
    </React.Fragment>
  )
}

Writing JSX

You can't write <p class="paragraph">Paragraph</p> as the name class is reserved in JavaScript, meaning it is already used in JavaScript. Instead you have to use className. <p className="paragraph">Paragraph</p>

You can add Javascript by wrapping it in curly braces.

<p>{ person.name }</p>

For writing comments in JSX you can't use HTML comments or double slashes. It have to be block quotes.

{ /* comment */ }

You write a piece of JavaScript in JSX so you wrap it in curly braces. You use /* instead of // because the double slashes will also comment out the closing curly brace.

Props

You can name them whatever as long as they don't overwrite any existing HTML attributes (or JSX variations).

<ComponentName anythingAtAll="burp" />

When you want to pass anything but a string you'll have to wrap it in curly braces.

<ComponentName age={31} updatePerson={functionName} />

Stateless Functional Component

A component that doesn't do anything but render some HTML and most likely some props.

const ComponentName = ( props ) => (
  <p>{ props.name }</p>
)

You can't use this.props as it no longer extends React.Component. But the props are still passed to the component but then as a parameter to the function. Notice the implicit return of the arrow function. No return or curly brackets necessary. You could also destructure the props like this:

const componentName = ( { name } ) => (
  <p>{ name }</p>
)

React Routing

You'll need to import BrowserRouter (along with Router and Switch) from react-router-dom.

import { BrowserRouter, Router, Switch } from 'react-router-dom';
const Router = () => (
  <BrowserRouter>
    <Switch>
      <Route exact path="/" component={ComponentName} />
      <Route exact path="/:productId" component={ProductComponent} />
    </Switch>
  </BrowserRouter>
)
  1. Stateless Functional Component
  2. BrowserRouter is the parent component
  3. Switch will make sure all Route components inside of it are looked at to search for a match for the path
  4. You pass a path and a component to the Route. If the path matches (matches exactly when using exact) the mentioned component will be used
  5. A variable path can be defined by passing an id like this: products/:productId (note the colon)
  6. If you don't use exact and have the path products then a path for products-collection will also match

Changing route from component

If we take the above Router into account, our component ComponentName now has access to some Router specific props that have been passed through. These are history, location and match. (In match you can find the productId if we're in the ProductComponent.) In history are several methods that allow you to manipulate the routing. For changing a route you'll need this.props.history.push().

this.props.history.push( '/about-us' );

Adding initial value to input

React doesn't like it when you pass the value attribute to an input without also defining onChange on the input. To pass an intial value to input you use the defaultValue attribute (or prop?)

<input type="text" defaultValue={getValue()} />
<input type="text" defaultValue="This is not like a placeholder" />

When you do pass only a value to an input, React will kind of disable the input where it will catch any keypress and undo the action.

<input type="text" value={this.props.value} />

React want you to add onChange where you will handle input changes and change the state. Once you've added the onChange attribute, the error will be gone, but it will still not be possible to type in the input box because React will see that the value does not change in the state and it will still undo the action. You have to actually change the value that you put into the input, in state.

<input type="text" value={this.props.value} onChange={this.handleValueChange} />

Inline events

Inline events are events on a component itself in the template which you would normally add in a JavaScript file with an addEventListener.

<button onClick="handleClick">Click me</button>

The handleClick() function doesn't have paranthesis (round brackets) because that would invoke the method immediately when the component mounts. The click event is passed automatically to the handleClick() function. When you do have to use parenthesis to pass some data, you can wrap the function in an arrow function.

<button onClick="() => handleClick( value )">Click me</button>

If you also want to pass the click event to the handleClick() function, this is now passed to the wrapping arrow function, so you'll have to pass it through from there.

<button onClick="( event ) => handleClick( event, value )">Click me</button>

Binding

React has it's own way of doing binding. This is of importance when trying to reference this within the React component that you are trying to create. You can reference this within the different React lifecycle hooks like render() and componentDidMount(). In any other functions that you add, this will be undefined.

class ComponentName extends React.Component {
  componentDidMount() {
    // this is instance of component
  }

  myFunction() {
    // this is undefined
  }

  render() {
    // this is instance of component
  }
}

You can bind your own functions to the component by using a constructor.

class ComponentName extends React.Component {

  constructor() {
    super(); // create the extended component first
    this.myFunction = this.myFunction.bind( this );
  }

  myFunction() {
    // this is now an instance of component and no longer undefined
  }
}

The issue with this is the fact that if you add multiple functions, you'll have to create multiple lines in the constructor to bind this to those functions. A different, and arguably a more elegant solution, is to create a property that is set to an arrow function. As a property, the arrow function is bound to the instance of the component.

class ComponentName extends React.Component {
  myFunction = (event) => {
    // this is now an instance of component and not undefined
  }
}

DOM References

React doesn't want you to directly touch the DOM to get like a value from an input, for example. There are several React specific ways of doing this, one of which is adding a reference.

class ComponentName extends React.Component {
  // create a reference
  myInput = React.createRef();

  myFunction = ( event ) => {
    // get value from input
    const inputValue = this.myInput.value.value;
  }

  render() {
    return (
      <input type="text" ref={this.myInput}>
    )
  }
}

Passing data

React only allows you to pass data down into components. It doesn't allow you to pass data up. This is all fine with static data that doesn't change while inside the deeper child components. But it gets trickier when a child component is supposed to change the data. The data in question will most likely be the state that lives in a higher level parent component. The state can only be actually changed inside that parent component. In order to change the state from the child component you can pass a function down into the child component that will be then invoked with a certain set of new data. The function in the parent component will then run with this data where it can alter the state.

class ParentComponent extends React.Component {
  state = {
    product: {
      name: '',
      price: 0
    }
  }

  const changeProduct = (product) => {
    // change the state here
    // see next chapter
  }

  render() {
    <ChildComponent changeProduct={this.changeProduct} />
  }
}
class ChildComponent extends React.Component {
  const handleSubmit = (event) => {
    this.props.changeProduct( { name: 'Product 1', price: 100 } )
  }
  render() {
    <form onSubmit={handleSubmit}>
      { /* Form content here */ }
    </form>
  }
}

The ChildComponent will have access to the function in the ParentComponent via props.

Changing state

The state inside a React component looks like a normal JavaScript object. But you're not allowed to update it as you would normally update an object in JavaScript. You're not allowed to directly mutate it.

class ComponentName extends React.Component {
  state = {
    product: {
      name: 'Product 1',
      price: 100
    }
  }

  const changeState = () => {
    // this is NOT allowed
    this.state.product.name = 'Product 2';
  }
}

Instead you have to use the built in setState hook and pass it a copy of the part of the state you wish to update.

class ComponentName extends React.Component {
  state = {
    product: {
      name: 'Product 1',
      price: 100
    },
    collection: 'New arrivals'
  }
  const changeState = () => {
    // create a copy of the part of the state we wish to update
    const product = { ...state.product }
    
    // update our copy of the state
    product.name = 'Product 2';

    // we could do that in one line with
    // const product = { ...state.product, name: 'Product 2' }

    // call the `setState` hook
    this.setState( { product: product } )
    // or: this.setState( { product } )
  }
}

The key attribute

When displaying state in JSX, to display a list of something or just a single item, React requires a unique value in a key attribute on all items. With the key attribute, React can directly reference the item to update in the (Shadow) DOM instead of having to fuzzy search for the element by going over all elements in the DOM.

render() {
  <p key={ person.id }>{ person.name }</p>
}

Lifecycle hooks

Lifecycle hooks are methods in React that you can call in your components that trigger at certain stages in the component. When it renders and when something in the component updates.

render()

The moment a component starts rendering.

componentDidMount()

The moment a component has rendered (has been mounted). It's the first time the component is done rendering.

componentWillUnmount()

The moment just before the component is removed from the DOM.

More lifecycle methods can be found in the React documentation.

Animating React

The react-transition-group package is a animation package for React. You can import it as such:

import { TransitionGroup, CSSTransition } from 'react-transition-group';

You can use the TransitionGroup as a wrapper around the elements that will contain the animation. But instead of adding it as another element, you can swap it for an existing element. Let's say you have an unordered list.

<ul className="unordered-list">
  <li>List item 1</li>
  { /* ...etc */ }
</ul>

You can change the ul to TransitionGroup, but because you still want all the traits of the ul, you can pass ul as a component property. When compiled to actual HTML, the TransitionGroup will still render out an actual ul element.

<TransitionGroup component="ul" className="unordered-list">
  <li>List item 1</li>
  { /* ...etc */ }
</TransitionGroup>

You can then wrap the list items in the other thing that we imported, the CSSTransition.

<TransitionGroup component="ul" className="unordered-list">
  <CSSTransition key={key} classNames="item" timeout={ { enter: 250, exit: 250 } }>
    <li>List item 1</li>
  </CSSTransition>
</TransitionGroup>

The CSSTransition component requires a key and a timeout. In this timeout property we can pass an object that will contain information about how long the animation duration will be when animating in (enter) and how long the animation duration will be when animating out (exit).

The CSSTransition component will add classes to whatever it wraps so you can animate it. For this is requires classNames input. Notice the "s" on the end. Although not mentioned explicitely in the course, this seems to be a prop for the CSSTransition component and will most likely be named as such because the className attribute is reserved for JSX.

<li class="item-enter item-enter-active"></li>
<li class="item-exit item-exit-active"></li>

The classes item-enter and item-exit are added to the items when the list item is generated and when it is about to be removed from the DOM. The classes item-enter-active and item-exit-active are added a split second after the first two I mentioned. This allows you to animate between two states.

.item-enter {
  background-color: red;
  transition: background-color .25s ease-in-out;
}
.item-enter-active {
  background-color: blue;
}

After the 250 milliseconds as determined in the timeout prop, the classes will be removed from the list item.

Prop types

You can determine the type of the value that needs to be passed into a prop. You can use a typed language like TypeScript, but you could also use PropTypes. You can import these from the prop-types package.

import PropTypes from "prop-types";

You can define the type of the props inside the component itself. In case of a functional component you declare the prop types after the component.

const ComponentName = props => (
  // JSX layout
)

ComponentName.propTypes = {
  propName: PropTypes.string.isRequired,
  otherPropName: PropTypes.number
}

In case of a class component you declare the prop types inside the class.

class ComponentName extends React.Component {
  static propTypes = {
    propName: PropTypes.string.isRequired,
    otherPropName: PropTypes.number
  }
  
  render() {
    ...
  }
}

With this bit of code we say the prop propName is of type string but also is required. The console will now issue an error when the prop is not added on the component and when anything but a string is passed into the prop. These errors will not show in the console in production, only in development. The PropTypes documentation will show you all the different types that are possible. Notable is that the prop type for booleans and functions are bool and func respectively. You can also determine what values an array holds with PropTypes.arrayOf(PropTypes.number). The same goes for an object with fields of a certain type of value: PropTypes.objectOf( PropTypes.string ). When your objects holds fields with different type of values, you can use a shape.

PropTypes.shape( {
  name: PropTypes.string,
  age: PropTypes.number
} )

Another component can also be passed into a prop. For this you can use instanceOf: PropTypes.instanceOf( ComponentName ). When your prop can be multiple things you can use oneOfType.

PropTypes.oneOfType( [
  PropTypes.string,
  PropTypes.number
] )

Or when your prop takes only specific values you can use oneOf.

PropTypes.oneOf( [ 'CREATE', 'UPDATE', 'DELETE' ] )
PropTypes.oneOf( [ 100, 250, 500 ] )

Again, the PropTypes documentation has more information.

Ejecting from create-react-app

Running create-react-app gives you a React boilerplate where it has handled all of the difficult stuff like webpack and babel. If you would look into your package.json file, you would only a few dependencies. A whole lot of other dependencies have been tucked away in the react-scripts. But you might run into a situation where you need some more control over the difficult stuff. For this you can eject from the create-react-app. In you package.json you'll find an eject script which you can run with npm run eject. It will ask for confirmation and after that it will have added all the dependencies it had previously hidden from the package.json file, back into the package.json file. You'll now also have two extra folders called config and scripts.