What I know about react so far part 1

In notebook:
Work Notes
Created at:
2019-06-26
Updated:
2019-08-07
Tags:
React libraries JavaScript

The very basics

  var mycomponent = React.createElement('h1', {}, 'The Text')

ReactDOM.render(mycomponent, document.getElementById('root'))

createElement can accept other components, so the third argument ('The Text'), you can pass it other functions that return other components. It can be an Array as well.

The props

The second argument ({}) passed to createElement, can be HTML attributes, and event listeners, or additional data (props), that you pass to a child component, ie if the first argument to createElement is another React component and not a String (e.g. div).

  function myh1(props) {
  return React.createElement('h1', {}, props.title)
}
React.createElement(myh1, {title: 'hello world'})

Debugging props

A simple "trick" to print out the props when you don't want to use the React Devtools.

    const Details = (props) => {
   // **** 1. print out the props.  ↴
    return <pre>
      <code>{JSON.stringify(props, null, 4)}</code>
      </pre>
  }

Functional components vs Class components

The above pattern is a functional component, a function returning some component. There's a second, more traditional way of creating classes, via the ES2015 Class syntax. It's the Class components that give more lifecycle methods, while functional components can use hooks and useeffects. More details on the class components below.

JSX

This is a de facto standard in the React (and front-end dev) community, basically everyone writes JSX instead of createElement, or whatever other virtual DOM creation function is provided by a library.

With JSX, the above component would be

  function myh1(props) {
  return (
    <h1>{props.title}</h1>
  )
}

return (
  <div>
    <myh1 title="Hello World"></myh1>
  </div>
)

Note, that JSX is still just a function call, and as such can only return ONE element, like a regular function would do.

You must wrap your JSX code in () parentheses, otherwise each line would be read as a new expression, as in JavaScript the newline is an explicit ;.

You must also close your tags.

The {} curly braces represent an expression, which is the right side of a an assignment (var x = ...). So statements like if(..)... cannot go there, but ternary expressions can ((a > 1) ? 'big' : 'small').

Mapping Lists with JSX

You can just .map lists, don't need to do .reduce or other accumulation/concatenation techniques. Note, to wrap your function in curly braces. Another "trick" to verify that your are dealing with a list is the Array.isArray test (if(Array.isArray(this.state.mylist)){do stuff...})

  var mylist = ['foo', 'bar', 'baz']
function listMylist(props) {
  return (
    <ul>
      {props.list.map(function showone(item) {
        return <li>{item}</li>
      })}
    </ul>
  )
}

return (
  <div>
    <listMylist list={mylist} />
  </div>
)

Strict Mode

React is deprecating some "stuff" and if you don't use strict mode, you can use these elements that will shortly be deprecated.

You can selectively make parts of your code strict ("new parts") or not strict ("old part").

To put parts of your app in strict mode:

  <React.StrictMode>
  // put part that you want to be strict mode
</React.StrictMode>

Strict mode will just give you some extra error messages in dev mode.

State management in React

This used to be done mostly by a singleton object (very beginning), or later Redux, Mobx, today (2019), Hooks and Contexts provide a simpler solution.

Hooks

There are several types of hooks in React, the convention is that their names stat with use....

To manage the state in your application, you call useState('a default value') that returns a tuple. The first item is the value of the state, the second is the updater.

  import React, { useState } from 'react'

function NameDisplay() {
const [name, setName] = useState('foo') // this is a hook
  // the name variable is now 'foo'

  return (
    <div>
      <input value={name} onChange={function updatethename(e) { return setName(e.target.value)}} />
    </div>
  )
}

The order of which you declare your hooks is important!. React uses the order of declaration, and this means that you should not conditinally add them (will create strange bugs, you will not updating what you intend to).

Custom Hooks

It's just a simple pattern to encapsulate more complex components. At the minimum, they return an Array of [state, ReactComponent, setState]

  function useLabeller(defaultState) {
  const [state, setState] = useState(defaultState)

  function Label() {
    return (
    <div>
      <h1>{state}</h1>
      <input value={state} onChange={e => setState(e.target.value)} />
    </div>
    )
  }
  return [state, Label, setState]
}

const [label1, FirstLabel, setFirstLabel] = useLabeller('foo')
const [label2, SecondLabel, setSecondLabel] = useLabeller('bar')
return (
  <div>
    <FirstLabel />
    <SecondLabel />
  </div>
)

Effects

These are actions that run after the first rendering has happened. So you would put asynchronous actions there, to not to slow down the first rendering (or make the UI unresponsive).

It replaces several of the lifecyle hooks, componentWillUnmount, componentDidMount, componentDidUpdate.

  const [label1, FirstLabel, setFirstLabel] = useLabeller('foo')

useEffect(function(){
  setTimeout(function(){
    setFirstLabel('long time has passed')
  }, 1000) 
  }) // note that we didn't pass
  // a second argument to useEffect
  // this would be a dependency

return (
  <div>
    <FirstLabel />
    <SecondLabel />
  </div>
)

Effects dependencies

If you call useEffect without adding a second argument it will run on every render.

The second argument is a list of dependencies, that will be monitored, and when they change, they will trigger the effect.

The React team advises you to add all dependencies, not just the state variables but the updaters as well (even though they will not be reassigned):

  useEffect(function(){
  setTimeout(function(){
    setFirstLabel('long time has passed')
  }, 1000) 
  }, [label1, setFirstLabel]) 

If you give an empty array ([]), useEffect will act like an initialise function, the effect will run once, after the first render.

Hooks async updates

So useEffect is like a watcher of a state element. If you want to do async stuff, you don't need effects, you can just call setWhatever from an async function.

  async function getItems() {
  const results = await myapi.get('list')
  setList(results)
}

<div>
  <button type="button" onClick={() => getItems()}>get list</button>
  // render list...
</div>

One way data flow

It's an important distinction in React, that data only flows in one direction, from the parent to the child component.

Routing

There are three main React router libs:

  • React router (Ryan Florence as collaborator)
  • Reach Router (written by Ryan Florence)
  • Novi

Brian Holt recommended to use Reach Router, as it's the most accessibility focused.

Route matching algorithm

React Router is like a switch statement, using the first matching route, while React Router has a scoring algorithm choosing the more specific route. So between path='/details/1 and path='/details/:id Reach router's scoring algorithm will choose /details/1 as more specific and so the one to render.

Independent routes

One page can have several routes, for example a side nav that uses different routes and so render differently than another component on the page.

Adding a Link with Reach Router

This will actually create a <a> anchor tag.

  import { Router, Link } from '@reach/router'

 cont App = () => {
    return (
      <React.StrictMode>
        <div>
         <header>
         // **** 1. wrap the head in a Link.  ↴
            <Link to='/'> 
              Adopt Me!
            </Link>
         </header>
        </div>
      </React.StrictMode>
    )
 }

Wrapping pages with Reach Router

In your App.js (root of the app), you want to create pages with urls:

  import { Router } from '@reach/router'

class App extends React.Component {
  render() {
    return (
      <div>
        <Router>
          <Results path="/" />
          <Details path="/details/:id" />
        </Router>
      </div>
    )
  }
}

:id is a variable for your path. Reach router uses a scoring system to decide which pages to send to, and according to Brian Holt it's bettern the React router.

Using navigate in Reach Router

You can use navigate('/') to send the user to a route. For example, when you do some async loading or an action that can fail, you can add it to the catch handler.

React Class Components

This is the most common way to write React apps, though in my opinion more cumbersome.

Class components can't use hooks, but they have lifecyle methods that give you even more granular update events. One of the selling point of Inferno js was that it could use lifecycle events with functional components.

About the super(props)

It calls the "parent" (super) constructor (React.Component), and passes it the props so that it can track it. (FrontendMasters)

  class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'foo'
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        name: 'bar'
      })
    }, 1000)
  }

  render () {
    //... do the rendering
    return (
    <div>
      <h1>{this.state.name}</h1>
    </div>
    )
  }
}

Rules of class components

Every Class component must have a render method.

componentDidMount

It's very similar to effects in the sense that it runs at startup, then stops. However it doesn't need dependencies. It's a great way to async update the state.

Async loading of data on startup

A common pattern is to set

  class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      loading: true
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        loading: false
      })
    }, 1000)
  }

  render () {
    if (this.state.loading) {
      return <h1>Loaging...</h1>

    } 
    return (
    <div>
      // render the loaded data
    </div>
    )
  }
}

this.props are immutable

Note that this.props is immutable, you are receiving it from the parent component and cannot modify it.

this.state vs this.props

this.props is handed down to you and is immutable, while this.state is meant for your component to hold it's own state. No other component can modify this this.state. So inside your component you always do this.setState

this.setState

You always use this.setState to update the component state. It does a shallow merge(only top level properties updated), equivalent to Object.assign(oldState, newtate).

While with hooks, you have a use.. for each piece of your state, with class components you just use this.setState for every element of your state in your component.

You cannot just do this.state.myval = 'foo', React will not be aware of the change. You have to explicitly call this.setState({myval: 'foo'}). This also has the advantage that it batches the calls, if you call setState hundred times, it will just do one re-render.

You only need to pass this.setState the properties you want to update, so just a subset of the complete state object and it will update just those properties.

Make sure to use arrow functions in class components!

This is necessary so that the this value stays bound to class and not to the call site.

Simpler class syntax with classProperties in Babel

Enable classProperties in Babel

Need to add some plugins to Parcel bundler:

npm install -D babel-eslint @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/preset-react

Parcel doesn't have these plugins, so you can override it and tell Parcel to use our own config.

Then

   // ****    .babelrc    ****

 {
  "presets": ["@babel/preset-react", "@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
 }

the presets part is for everything needed to compile React/jsx. the @babel/preset-env will transpile to the target environment we specified in package.json previously (browserslist property).

The simpler class syntax

With the above setup, you can use the ES2019 (or ES2020) syntax to simplify the class creation steps:

   class Details extends React.Component {
  // **** 1. just set the state with the new syntax.  ↴
  state = { loading.true }
  componentDidMount() ...
  }

getDerivedStateFromProps

As mentioned before, this.props is immutable, but we can derive some elements from it to use it in our this.state. It must be a static function. This means that each time the this.props updates, the this.state will update as well. You can do some data transformation inside getDerivedStateFromProps, and usually you put the more calculation intensive ones here and not inside render.

Note also, that inside getDerivedStateFromProps, you don't reference the this.state, but just return an object (like in this.setState).

  class MyComponent extends React.Component {
  state = {
    names: []
  }

  static getDerivedStateFromProps( props ) {
    names = props.names.map(n => n.toUpperCase())
    return { names }
  }

  render() {
    return (
      <div>
        {this.state.names.map(name => (<h1>name</h1>))}
      </div>
    )
  }
}

The static methods in React

First, it means that it's a static property, that lives on the class and so each copy of the component won't create a new function, but will delegate to the base function. It also means that it's accesible from the outside, via MyComponent.getDerivedStateFromProps, otherwise you have to instantiate your class via const c = new MyComponent; c.render, so render is now available.

Class components and the this pointer

  class MyComponent extends React.Component {
  state = {
    counter: 0
  }

  handleClick(event) {
    // this pointer problem, see below
    this.setState({
      counter: this.counter++
    })
  }

  render() {
    return (
      <div>
      <button type="button" onClick={this.handleClick}></button>
      </div>
    )
  }
}

The problem with the above pattern is that the this pointer will most likely point to the window (global) element (dynamic scoping) inside handleClick and not to our component.

You could "cheat" and bind the this keyword to our component:

  constructor (props) {
  super(props)
  this.handleIndexClick = this.handleIndexClick.bind(this)
}

Or, use the fat arrow syntax which does lexical binding of the this pointer:

  handleClick = event => {
  this.setState(...)
}

Catching Errors in Components

You can create a class component, that you can use to catch errors. This is not possible with hooks only class components.

   class ErrorBoundary extends Component {
  state = { hasError: false }

  static getDerivedStateFromError () {
    return { hasError: true }
  }

  componentDidCatch(error, info) {
    console.error('ErrorBoundary caught an error', error, info)
  }

  render () {
      if(this.state.hasError) {
        return (
          <h1> Seems to an error...</h1>
        )
      }

      // if no error, then this component is just a pass through
      return this.props.children
    }
  }

 export default ErrorBoundary

Instead of console logging the error, you could use a service like Sentry.io to monitor your app.

To use the Error Boundary as a middleware

   import ErrorBoundary from './ErrorBoundary'

export default function DetailsWithErrorBoundary(props) {
   return (
   <ErrorBoundary>
    <Details {...props} />
   </ErrorBoundary>
   )
}

Note, that you need to spread the props for the child component you are passing to.

And now you can throw errors in your Details component:

   // ****    Details.js    ****

 class Details extends React.Component {
  state = { loading: true }
  componentDidMount() {
    throw new Error('lol')
    ...
  }
 }

componentWillUnmount

Let's you clean up things before the component leaves the DOM.

Using componentWillUpdate

This will run every time the component gets new state or new props.

getDerivedStateFromError

This is a lifecyle method as well, called when React catches an error.

Context

Context is a new concept in React which has just became stable enough to replace other state management techniques like Redux.

It's a global store for your application data, where child components can also update the state that may be used by a parent component. You don't need to pass down the props to your child component. Instead you wrap your app with <ThemeContext.Provider>.

   import { createContext } from 'react'

 // we will give a hook like shape
 // but it can be anything you want
 const ThemeContext = createContext(["green", () => {}])
 // the empty function is just hooks tuple, later you may 
 // want to extend it (this is the updater function)

 export default ThemeContext

then wrap a part of your app with the context.provider:

   // **** 1. import.  ↴
 import ThemeContext from './ThemeContext'
 import React, { useState } from 'react' // also import useState

 const App = () => {
  const themeHook = useState('darkblue') // adding some default value
  // we don't destructure [color, setColor], just grab the whole thing (themeHook)
  // themeHook will be the passed Context's (default) value
  // and setter (the tuple, [theme, useTheme])
  return (
      // **** 2. wrap the whole component.  ↴
      // and pass the Context down
      <ThemeContext.Provider value={themeHook}>
        <div>
          ...
        </div>
      </ThemeContext.Provider>
  )
 }

Context with Hooks

You can use the Context passed from a parent component via hooks. You still need to import the ThemeContext module file to reference it, the context will not be magically present (without any declaration in your file) in your component.

Use it with useContext, passing it your context object: const [theme, setTheme] = useContext(ThemeContext) And now you can use theme and setTheme in your code:

  import React, { useState, useEffect, useContext } from 'react'
import ThemeContext from './ThemeContext'

const SearchParams = () => {
  const [theme, setTheme] = useContext(ThemeContext) 

  async function requestPets() {
    <div class="search-params">
      <button style={{backgroundColor: theme}}>Submit</button>
    </div>
  }
}

Context with Classes

You need to use Context via Context.Consumer, which provides a function that receives the Context as first argument:

  import ThemeContext from './ThemeContext'
class Details extends React.Component {
  render() {
    <ThemeContext.Consumer>
        {(themeHook) => (
          <button
            style={{ backgroundColor: themeHook[0] }}
            // themeHook tuple provides the theme AND
            // the setTheme
          >
            Hello world
          </button>
        )}
    </ThemeContext.Consumer>
  }
}

Portals

When you want to render your component to another target in the DOM with your component (instead of inside the parent component). For example, you want to open a modal popup and it needs to be at the end of document.body.

The idea is that you first craete a Modal component, then you import it in your components and render into it.

First, you add a <div id="modal"></div> element to your HTML file.

With Class components

  // ****    Modal.js    ****

import React from 'react'
import { createPortal } from 'react-dom'

const modalRoot = document.getElementById("modal");

class Modal extends React.Component {
  constructor(props) {
    super(props)

    this.el = document.createElement("div")
   }

   componentDidMount() {
    modalRoot.appendChild(this.el)
   }

   // to clean up memory...
   componentWillUnmount() {
    modalRoot.appendChild(this.el)
   }

   render () {
    return createPortal(this.props.children, this.el)
   }
}

export default Modal

About this.props.children

There are two reserved words for properties, this.props.keys and this.props.children. this.props.children is whatever child components are passed to your component, for example <Router><Foo /></Router>, in this case <Foo /> is the "children".

With functional components

   // ****    Modal.js    ****

import React, { useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'

// grab the element
// you want to render to,
// from the DOM ↴
const modalRoot = document.getElementById("modal");

const Modal = ( { children }) => {
  const elRef = useRef(null)
  // make sure we don't create a new div
  // see useRef section below
  if (!elRef.current) {
     const div = document.createElement('div')
     elRef.current = div
  }

  useEffect(() => {
    const modalRoot = document.getElementById('modal')
    modalRoot.appendChild(elRef.current)

    return () => modalRoot.removeChild(elRef.current) // this is
    // the cleanup function, see below
  }, []) // we add an empty array to declare 0 dependencies
  // and so the effect will only run once

  return createPortal(<div>{children}</div>, elRef.current) // the
  // div wrapper is jst for styling purposes
}

export default Modal

useRef

We need to destroy our modal element when we close, otherwise we will leak memory. Since we have re-renders, we need a way to reference the same element in the DOM.

Refs in React

It's for integrating with other libraries like D3. Add a ref prop: <div ref={el => this.myelem = el} />

  class D3Wrapper extends React.Component {
  componentDidMount () {
    d3.init(this.el)
  }
  componentWillUnmount () {
    
  }
  shouldComponentUpdate () {
    return false
  }
  render () {
    return (
      <div>
        <div ref={el => this.el = el}></div>
      </div>
    )
    
  }
}


The cleanup function of useEffect

If you return a function from useEffect, it is the cleanup function.

Rendering to a different part of the DOM.

It's the createPortal function that does retargeting of the rendering.

Using the Modal (portal)

Basically you just need to render it, the below code shows a more complex example where you can also toggle the rendering (opening) of a portal component.

  // **** 1. import Modal.  ↴
import Modal from "./Modal";
class Details extends React.Component {
  // **** 2. add showModal state.  ↴
  state = { loading: true, showModal: false };
  // **** 3. add the togglemodal function.  ↴
  toggleModal = () => this.setState({ showModal: !this.state.showModal })
  // just toggle the modal state (above line)
  render() {
    // **** 4. grab showModal.  ↴
    const { showModal } = this.state;

    return (<div>
    <button
     // **** 6. add the clich handler.  ↴
      onClick={this.toggleModal}
    >
      Adopt {name}
    </button>
    // **** 5. show the modal.  ↴
    {
      // this is how you render conditionally in React
      showModal ? (
        <Modal>
          <div>
            <h1>Would you like to adopt me {name}?</h1>
            <div className='buttons'>
              <button onClick={this.adopt}>Yes</button>
              <button onClick={this.toggleModal}>Opens the modal</button>
            </div>
          </div>
        </Modal>
      ) : null // this means render nothing (when not showing the modal)
    }
    </div>)
  }

}

What's great about this is that the events will still bubble up to your parent components (or the current component), even though the component is rendered outside the "normal" DOM tree of your app.

More on handlers and interactivity

You add onClick, onBlur, onChange, etc then define methods on your class, but no need to make them static: The event object it receives is a synthetic event, but you can treat it is a regular DOM event.

Arrow functions: note that you need to use arrow functions so that this.setState is bound correctly to your component.

  
//.
handleMyClick = (event) => {
  this.setState({
    active: event.target.dataset.index
  })
}

To send "data" via a DOM element (React node), you can use the dataset attribute. The above code would set the this.state.active index to the selected item in the DOM.

Aside, coercing a String to a number

You can do +myvar, so "1" will become 1.

Forms in react

For forms, you need to use htmlFor instead of the for DOM attribute for the <label> elements to target an <input/>.

Disabling a <select> element

    <select
    disabled={!this.state.items.length}
  />

This will set the disabled attribute to true if there are no items in the list.

Data-binding in React

React has one-way data binding, so you need to "manually" update the state with your own handler (eg. handleMyInputChange.

  class MyComponent extends React.Component {
  state = {
    name: ''
  }
  handleMyInputChange = event => {
    this.setState({
      name: event.target.value
    })
  }
  render() {
    return (
      //...
      <input onChange={this.handleMyInputChange} />
      )
  }
}

For accessibility, always add an onBlur handler whenever you use onChange!

Batching update calls with event handlers

React batches update calls, and so there's a pattern to call your async function (getWhatever from a server), as a second argument to this.setState.

    handleMyInputChange = event => {
    this.setState({
      name: event.target.value
    },
    // add this as second argument
    this.getWhateverFromServer
    )
  }
  getWhateverFromServer() {
    getData().then(result => this.setState({name: result.name}))
  }

There's a new method coming to React called deferSetState, that will allow for even more granual updates and setting priorities, but it was not yet available at the time of the course.

Using this.setState directly or updater function

You can either do

  this.setState(on: !this.state.on)

// or use an updater function
// it receives the previous state as its first argument

this.setState(
  (currentState) => {
    return {on: !currentState.on}
  },
  // (adding a second, callback function)
  // for the users of your component
  () => {
    this.props.onToggle(this.state.on)
  }
)