Skip to content

Latest commit

 

History

History

react

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Why do we use a framework?

There are many reasons, but one is that interacting with the DOM is one the most expensive operations you can do in Javascript in terms of performance. We should use it only when we have to, and this is one of the key principles that SPA frameworks try to take care of for us. For example, React will calculate exactly which nodes in the DOM need to be re-rendered and only re-render those.

Lifecycle Methods

Unexpected Side Effects

As of React 16.3, some commonly used lifecycle methods have been deemed UNSAFE by the React team and will be completely deprecated by React 17. The problem with some of the lifecycle methods is that they were often misused or misunderstood, which led to cases where side effects (state/prop changes) were not behaving as expected.

First, we need to understand the work that React does to update our UI:

  • The render phase. This determines what changes need to be made in the DOM (for react-dom at least). React will call render and compare the result to the previous render.
  • The commit phase. This is when React actually applies our changes. In the case of react-dom, this is when React inserts, updates and removes DOM nodes. During this phase, lifecycle methods like componentDidMount and componentDidUpdate will be called.

Note: For async rendering (coming soon in React 16.x), the rendering work will be paused and resumed to avoid blocking the browser. The main reason for this is that the commit phase is usually very fast, while rendering can be slow. React may invoke the render phase several times before actually committing the work (i.e. updating the UI).

During the render phase, React can call any of the following lifecycle methods:

  • constructor
  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState

The above methods can and most likely will be called more than once during the render phase, so it's important that they do not contain side effects. Ignoring this can lead to memory leaks and invalid application states. It can often mean that the rendered state of the app is non-deterministic (we don't know what the output will be).

React 16.3: How to handle side effects?

Initializing state

class ExampleComponent extends React.Component {
  state = {
    currentColor: this.props.defaultColor,
  }
}

Fetching external data

class ExampleComponent extends React.Component {
  state = { data: null }

  componentDidMount() {
    this.request = fetchData()
      .then((data) => {
        this.request = null
        this.setState({ data })
      })
  }

  componentWillUnmount() {
    if (this.request) this.request.cancel()
  }

  render() {
    if (this.state.data === null) {
      // Render loading state
    } else {
      // Render UI with data
    }
  }
}

Previously, some people would do this kind of thing using componentWillMount. If the data is not immediately available when componentWillMount fires, the first render will still show a loading state regardless of where you initiate your fetch.

Updating state based on props

class ExampleComponent extends React.Component {
  state = {
    scrollingDown: false,
    lastRow: null,
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.currentRow !== prevState.lastRow) {
      return {
        scrollingDown: nextProps.currentRow > prevState.lastRow,
        lastRow: nextProps.currentRow,
      }
    }

    return null
  }
}

What's happening here is not too important, but the point is that getDerivedStateFromProps should now be used where componentWillReceiveProps was often used before.

Performing side-effects according to prop changes

class ExampleComponent extends React.Component {
  componentDidUpdate(prevProps, prevState) {
    if (this.props.isVisible !== prevProps.isVisible) {
      logVisibleChange(this.props.isVisible);
    }
  }
}

Before 16.3, we would often invoke side effects in componentWillReceiveProps. This method will often get called multiple times during a single update, so it's important to ensure it's only called once per update.

Reading DOM properties before an update

class ScrollingList extends React.Component {
  listRef = null;

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Are we adding new items to the list?
    // Capture the current height of the list so we can adjust scroll later.
    if (prevProps.list.length < this.props.list.length) {
      return this.listRef.scrollHeight;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If we have a snapshot value, we've just added new items.
    // Adjust scroll so these new items don't push the old ones out of view.
    // (snapshot here is the value returned from getSnapshotBeforeUpdate)
    if (snapshot !== null) {
      this.listRef.scrollTop +=
        this.listRef.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <div ref={this.setListRef}>
        {/* ...contents... */}
      </div>
    );
  }

  setListRef = ref => {
    this.listRef = ref;
  };
}

Again, what's happening here is not too important, but it's important to see how getSnapshotBeforeUpdate works. This method will get called immediately before mutations are made to the DOM and componentDidUpdate is called immediately after mutations are made.

Parent & Child Communication

Instance Methods

In any frontend framework, this topic always comes up and questions get asked about it all the time. In React there are several ways you can achieve communication between parent and child components, so here are just a few:

class TheChild extends Component {
  childMethod() {
    return 'hello'
  }
}

class TheParent extends Component {
  render() {
    return <TheChild ref={(foo) => { this.foo = foo }} />
  }

  componentDidMount() {
    const x = this.foo.childMethod()
    // x is now 'hello' thanks to TheChild's `childMethod`
  }
}

When rendering the child, we attach a ref (reference) with a name of our choosing so we can refer to the child throughout our parent component.

Callback Functions

We can also pass a function from the parent to the child as props. The child can use this function to communicate with its parent. The Parent could also render several children, which can all have the ability to update a value within the parent.

const TheChild = ({ parentFunc }) => <div>{props.parentFunc()}</div>

class TheParent extends Component {
  constructor(props) {
    super(props)
    this.parentFunc = this.parentFunc.bind(this)
  }

  parentFunc() {
    return 'Hello from TheParent'
  }

  render() {
    return <TheChild parentFunc={this.parentFunc} />
  }
}

Component vs. PureComponent

What is PureComponent? The only difference between it and Component is that PureComponent will also handle the shouldComponentUpdate method for us. PureComponent performs a shallow comparison on both props and state to decide if the component should update or not. For a Component, it will re-render by default any time props or state changes.

Shallow Comparison

When comparing props and state, it will check that primitives have the same values and that references are the same for Arrays and Objects. This behaviour encourages immutability when it comes to Arrays and Objects, and always return new references rather than mutating the existing value.

Let's say you have an Array being passed into a PureComponent, and you want to add a new item to the Array. If you just wrote myArray.push(1), the PureComponent would see a reference to the same Array, and not update. However, if we used a function that returned an entirely new Array with the new item added to it, our PureComponent would indeed update.

Don't bind functions inside render

class Parent extends Component {
  likeComment(userID) {
    // ...
  }

  render() {
    return (
      // ...
      <Comment likeComment={() => this.likeComment(user.id)} />
      // ...
    )
  }
}

Consider this simple Parent which renders a Comment. If Parent re-renders because of a change to another prop or state value, the entire Comment component will also re-render. Every time Parent re-renders, it creates an entirely new function and passes it in as the likeComment prop. If we had a list of comments, you can see how this could negatively impact performance.

class Parent extends Component {
  likeComment = (userID) => {
    // ...
  }

  render() {
    return (
      // ...
      <Comment likeComment={this.likeComment} userID={user.id} />
      // ...
    )
  }
}

class Comment extends PureComponent {
  // ...
  handleLike() {
    this.props.likeComment(this.props.userID)
  }
  // ...
}

The same logic also applies when doing something like this:

<Comments comments={this.props.comments || []} />

What we are expecting is that if there are no comments passed into the component, it will send an empty Array to the component. What actually happens here is that a new Array with a new reference is going to be used every time the render() method is ran. Instead, we can create a constant outside of our component (e.g. const defaultComments = []) and reference that.

Don't derive data in render

For the same reason as above, we also shouldn't create new Arrays or Objects directly inside the render() method.

render() {
  const { posts } = this.props
  const topTen = posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
  return //...
}

Here we're creating a new Array called topTen when the component renders. topTen will have a brand new reference each time the component re-renders, even if posts hasn’t changed. This will then re-render the component unnecessarily.

There are a couple of ways we could solve this. The first would be to only set topTen when we know the value of posts has changed:

componentWillMount() {
  this.setTopTenPosts(this.props.posts)
}
componentWillReceiveProps(nextProps) {
  if (this.props.posts !== nextProps.posts) {
    this.setTopTenPosts(nextProps)
  }
}
setTopTenPosts(posts) {
  this.setState({
    topTen: posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
  })
}

Another approach is to use recompose to create a new prop called topTen which is derived from the posts prop:

export default compose(
  withPropsOnChange(['posts'], ({ posts }) => {
    return {
      topTen: posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
    }
  })
)(Component)

And finally, you could consider using reselect to create selectors which return the derived data from Redux.

Portals

Portals were a added in React 16, and straight away libraries such as react-modal used Portals to improve their libraries. Portals allow us to create links between a component and an element. In the case of creating a modal, most people that have used a modal library will know how annoying styling, controlling state, sharing state and HTML markup can be. The idea for Portals is that you can create one <div> outside of your React <div> and simply create a Portal to that <div> from inside your app.

Let's take a look at an example:

class ExternalPortal extends Component {
  constructor(props) {
    super(props);
    // STEP 1
    this.containerEl = document.createElement('div');
    this.externalWindow = null;
  }

  componentDidMount() {
    // STEP 3
    this.externalWindow = window.open('', '', 'width=600,height=400,left=200,top=200');

    // STEP 4
    this.externalWindow.document.body.appendChild(this.containerEl);

    // STEP 5
    this.externalWindow.addEventListener('beforeunload', () => {
      this.props.handleClosePortal();
    });
  }

  componentWillUnmount() {
    this.externalWindow.close();
  }

  render() {
    // STEP 2
    return ReactDOM.createPortal(this.props.children, this.containerEl);
  }
}

What's happening here?

  1. Create an empty <div>
  2. Create a Portal with this.props.children as its children, and render those inside our new empty <div>
  3. Upon mounting, create a new external window
  4. We now have a <div> with a Portal attached to it, which is rendering whatever children are passed into it. Here, we just append the body of our new external window with our <div> Portal
  5. Lastly, we listen for whenever this external window is closed. When it's closed, we'll call this.props.handleClosePortal which should be passed in by its parent and trigger some side-effects in the parent

So, now we have a Portal that when used, will render whatever its children are and call a parent prop when closed. Most importantly, it's rendered somewhere completely outside of the <div> which holds our React app!

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0,
      showPortal: false,
    };
  }

  componentDidMount = () => {
    window.addEventListener('beforeunload', () => this.closePortal());

    window.setInterval(() => {
      this.setState(state => ({
        counter: state.counter + 1,
      }));
    }, 1000);
  }

  togglePortal = () => this.setState({ showPortal: !this.state.showPortal })

  closePortal = () => this.setState({ showPortal: false })

  render() {
    return (
      <div>
        <h1>Counter: {this.state.counter}</h1>

        <button onClick={this.togglePortal}>`${this.state.showPortal ? 'Close' : 'Open'} Portal`</button>

        {/* Use our Portal instance and pass in `this.state.counter` */}
        {this.state.showPortal && (
          <ExternalPortal handleClosePortal={this.closePortal} >
            <h1>`Counter in a Portal: ${this.state.counter}`</h1>

            <button onClick={() => this.closePortal()}>Close!</button>
          </ExternalPortal>
        )}
      </div>
    );
  }
}

Now we have a straight forward App component, which has some local state to store showPortal, counter and some methods to toggle/update those state properties. When showPortal is true, we render our ExternalPortal and pass in children which take advantage of App's local state, and also pass in App's method to close the Portal. Magic! ✨

HOCs

A higher-order component (HOC) is a function that takes a component and returns a new component. Typically, HOCs are helpers or utilities which you apply to a component to give it some additional functionality. Redux's connect() is a good example of a HOC, which is invoked by calling connect(...)(MyComponent) and will help connect your component to Redux.

Intro to HOCs

Here's a really simple example, where I have a <User /> component which takes in a name and renders it. Below that, I have 2 more cases that I want to achieve. With alwaysBob, I want to be able to create a <User /> where the name prop is always set to "Bob". With neverUpdate, I want to be able to create a <User /> which never re-renders, even when receiving new props.

const User = ({ name }) => <div className="User">{ name }</div>
const User2 = alwaysBob(User)
const User3 = neverUpdate(User)

const App = () =>
  <div>
    <User name="Tim" />
    <User2 name="Joe" />
    <User3 name="Steve" />
  </div>

alwaysBob will take in a prop and simply return the BaseComponent with {...overrideProps} after {...props} to override the values.

const overrideProps = (overrideProps) => (BaseComponent) => (props) =>
  <BaseComponent {...props} {...overrideProps} />

const alwaysBob = overrideProps({ name: 'Bob' })

neverUpdate will return a class implementation of the BaseComponent with shouldComponentUpdate set to false to force it never to update

const neverUpdate = (BaseComponent) =>
  class extends Component {
    shouldComponentUpdate() {
      return false
    }

    render() {
      return <BaseComponent {...this.props} />
    }
  }

Easy!

Recompose: Lifecycle Hooks

We can replace the neverUpdate HOC above with a much simpler implementation thanks to recompose.

import { lifecycle } from 'recompose'

const neverUpdate = compose(
  lifecycle({
    shouldComponentUpdate() {
      return false
    }
  })
)

const UserWithNeverUpdate = neverUpdate(User)

Recompose: Apply state/methods

In this example, I'm going to add a state and some methods to the component. Normally, you'd have to go back and change the component to a Class component, then add some methods, then take care of binding the methods, then keep track of updating the state etc. Long story short, your component will look less and less like a simple UI component. Recompose, can help to add class-like methods to our functional components.

Take this Card:

const Card = ({ opened, handleClick, title, picture, description }) => {
  return (
    <div className={ opened ? 'card open' : 'card closed' }>
      <div className="header" onClick={handleClick}>
        {title}
      </div>

      <div className="body">
        <img src={picture} />
        <p>{description}</p>
      </div>
    </div>
  )
}

It applies a dynamic class to the <div> container depending on if this.props.opened is true or false. How is it updated? The Card's header has an onClick event attached to it which will call this.props.handleClick. Enter recompose again:

const enhance = compose(
  withState('opened', 'setOpened', true),

  withHandlers({
    handleClick: props => event => {
      // setOpened is applied in withState()
      props.setOpened(!props.opened)
    },
  })
)

export default enhance(Card)

withState is called with stateName, stateUpdaterName, initialState and will essentially pass the first two as props to the component and the third as defaultProps. withHandlers is just a nice way of adding a method to your component's props just like you would with a class method. There are a couple of benefits from doing this with recompose however. Recompose will take care of making sure a new handler is not created with every render. If you were to define your handler just before your return(...), it would create a new instance of that function on every render, which in a large application could become very costly. It could also be ignored by optimzations such as shouldComponentUpdate() which tries to prevent re-rendering from happening.

Recompose: Dynamic Rendering

Let's say we have a list:

const App = ({ zombies }) => {
  return (
    <div>
      {
        zombies.map(zombie => (
          <Card
            key={zombie.title}
            title={zombie.title}
            picture={zombie.picture}
            description={zombie.description}
          />
        ))
      }
    </div>
  )
}

I want to render a <Spinner /> if the data is still being loaded, and now that I think about it I also want to fetch the data when the App is mounted.

const enhance = compose(
  lifecycle({
    componentDidMount() {
      this.props.fetchZombies()
    }
  }),

  branch(
    ({ zombies, ...others }) => zombies.length === 0,
    renderComponent(Spinner)
  )
)

export default enhance(App)

As easy as that. branch() allows us to make sure our functional component remains a really simple UI component, and moves the dynamic rendering logic into a separate function. As we saw before, we can also use lifecycle() to access the lifecycle methods of a component without having to turn it into a class component.

Recompose: Formatting existing props

Let's say you have a very simple <Date /> component which simply renders a UI for a given date.

const Date = ({ date }) => <h1>`My DOB is ${date}`</h1>

As simple as you can get, it takes in a date and renders it. But what happens if we decide we want to format the date? We can pass in the date in the necessary format to <Date />, or we can simple format the date locally. This is the better approach as we may not want to change date's data structure; we just want to change how the UI renders the data.

const getFormattedDate = d => `${d.getDate()}/${d.getMonth() + 1}/${d.getFullYear()}`

export default compose(
  withPropsOnChange(['dob'], ({ dob }) => {
    return { dob: getFormattedDate(dob) }
  })
)(Date)

Now, Recompose will take care of creating this formatted prop whenever the dob prop changes.

Render Props

Background:

Diving straight in, here's a HOC which gives a component props.mouse with x and y co-ordinates.

const withMouse = (Component) => {
  return class extends React.Component {
    state = { x: 0, y: 0 }

    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }

    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
          <Component {...this.props} mouse={this.state}/>
        </div>
      )
    }
  }
}

const App = React.createClass({
  render() {
    // Instead of maintaining our own state,
    // we get the mouse position as a prop!
    const { x, y } = this.props.mouse

    return (
      <div style={{ height: '100%' }}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})

// Just wrap your component in withMouse and
// it'll get the mouse prop!
const AppWithMouse = withMouse(App)

ReactDOM.render(<AppWithMouse/>, document.getElementById('app'))

Now we can wrap any component and it'll receive this.props.mouse. So what's wrong with it?

  • Indirection: With multiple HOCs being used together, we can very easily be left wondering where our state comes from and wondering which HOC provides which props.
  • Naming collisions: Two HOCs that try to use the same prop name will collide and overwrite one another, and it's actually quite annoying because React won’t warn us about the prop name collision either.
  • Static composition: HOCs have to be used outside of React's lifecycle methods, as we see with AppWithMouse above, which means we can't do much in the way of making our HOCs dynamic.

Seeing the light of render props

A render prop is a function prop that a component uses to know what to render

So what does this mean? Well, the idea is this: instead of “mixing in” or decorating a component to share behaviour, just render a regular component with a render prop that it can use to share some state with you. Here's our withMouse HOC as a render prop:

class Mouse extends Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }

  state = { x: 0, y: 0 }

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}

const App = () => (
  <div style={{ height: '100%' }}>
    <Mouse render={({ x, y }) => (
      <h1>The mouse position is ({x}, {y})</h1>
    )}/>
  </div>
)

Now hold on, our Mouse component is just calling this.props.render(this.state)? Mouse is now a component that just exposes its internal state to a render prop. This means that App can render whatever it wants with that state. Does this solve any of the issues we had with HOCs?

  • Indirection: Yes. We no longer wonder where our state is coming from as we can see them in the render prop's arguments, and we can see it comes from <Mouse />
  • Naming collisions: Yes. We are not merging any property names together anymore, and neither is React.
  • Static composition: Yes. Everything is happening inside the render method, so we get to take advantage of the React lifecycle.

Function as Child components

We've already looked at render props, so now let's look at the similar concept of functions as child components. Here's a simple example:

const MyComponent = ({ children }) => <div>{children('Declan')}</div>

// Usage
<MyComponent>
  {(name) => <div>{name}</div>}
</MyComponent>

<MyComponent>
  {(name) => <img src="/my-picture.jpg" alt={name} />}
</MyComponent>

As you can see, it's really easy to re-use these components as MyComponent is just exposing some data to whatever its children function renders.

A more complicated example

Next, we'll create a <Ratio> component that listens for resize events and gives the device dimensions to its child.

The boilerplate:

class Ratio extends Component {
  render() {
    return (
      {this.props.children()}
    )
  }
}

Ratio.propTypes = {
  children: React.PropTypes.func.isRequired,
}

Now we have a simple component, and we explicitly tell the user of it that we're expecting a function as props.children. Now let's add some internal state to Ratio:

class Ratio extends React.Component {
  constructor() {
    super(...arguments)

    this.state = {
      hasComputed: false,
      width: 0,
      height: 0,
    }
  }

  getComputedDimensions({ x, y }) {
    const { width } = this.container.getBoundingClientRect()

    return {
      width,
      height: width * (y / x),
    }
  }

  componentWillReceiveProps(next) {
    this.setState(this.getComputedDimensions(next))
  }

  componentDidMount() {
    this.setState({
      ...this.getComputedDimensions(this.props),
      hasComputed: true,
    })

    window.addEventListener('resize', this.handleResize, false)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize, false)
  }

  handleResize= () => {
    this.setState({
      hasComputed: false,
    }, () => {
      this.setState({
        hasComputed: true,
        ...this.getComputedDimensions(this.props),
      })
    })
  }

  render() {
    return (
      <div ref={(ref) => this.container = ref}>
        {this.props.children(this.state.width, this.state.height, this.state.hasComputed)}
      </div>
    );
  }
}

Ratio.propTypes = {
  x: React.PropTypes.number.isRequired,
  y: React.PropTypes.number.isRequired,
  children: React.PropTypes.func.isRequired,
}

Ratio.defaultProps = {
  x: 3,
  y: 4
};

We did a lot here. We basically have an eventListener listening for resize events, and whenever it receives an event it just calculates the ratio based on the device width. After, we pass all that into our children function. Now, we can use it however we want:

<Ratio>
  {(width, height, hasComputed) => (
    hasComputed
      ? <img src="/my-image.png" width={width} height={height} />
      : null
  )}
</Ratio>

<Ratio>
  {(width, height, hasComputed) => (
    <div style={{ width, height }}>Hello world!</div>
  )}
</Ratio>

<Ratio>
  {(width, height, hasComputed) => (
    hasComputed && height > TOO_TALL
      ? <TallThing />
      : <NotSoTallThing />
  )}
</Ratio>

Positives:

  • The user can choose what they do with the properties passed into children
  • The user can choose what property names to use as it's just a function. A lot of HOCs will force you to use their property names
  • No naming collisions as there isn't any binding connection between the HOC and its child. Instead you could use 2 HOCs together that both calculate the device width and have no issues at all
  • HOCs remove constants! See below for an example:
MyComponent.MyConstant = 'HELLO'

export default connect(..., MyComponent)

Normally, a HOC would remove your MyConstant unless your HOC manually re-implements it.

Another simple use case for render Props

This time, I'm going to have a simple <List /> component which takes in a URL and renders a list. Why is this useful? Well, let's see:

class List extends React.Component {
  state = {
    list: [],
    isLoading: false,
  }

  fetchData = async () => {
    const res = await fetch(this.props.url)
    const json = await res.json()

    this.setState({
      list: json,
      isLoading: false,
    })
  }

  componentDidMount() {
    this.setState({ isLoading: true }, this.fetchData);
  }

  render() {
    return this.props.render(this.state);
  }
}

Now we have a component which takes care of fetching the data and applying any internal states such as loading. It's important to minimize duplicated code in a project to promote consistency and simplicity within the codebase (and UI). It also reduces the number of points of origin for an error when debugging, which is always good.

Here's how we would use that <List /> component:

<List
  url="https://api.github.com/users/declanelcocks/repos"
  render={({ list, isLoading }) => (
    <div>
      <h2>My repos</h2>
      {isLoading && <h2>Loading...</h2>}

      <ul>
        {list.length > 0 && list.map(repo => (
          <li key={repo.id}>
            {repo.full_name}
          </li>
        ))}
      </ul>
    </div>
  )} />

If we know that every component will have a consistent Loading... message, we could also extend our List to take care of this too.

Provider

Use React, and you're almost guaranteed to run into some form of <Provider> component. Redux, MobX or any theming library all use this pattern in order to pass down props to every component.

Context

First, we need to understand what React's Context is, as that's what all of these Providers are using. React often encourages people not to use context, but it's a powerful feature which can be great for making all components aware of things such as state or themes. You can also use context on a much smaller scale than the entire app. Consider the following:

--A--B
   \
    C--D--E

Imagine that component B and E both relied on a variable or method used in component A. With no state management library such as Redux, we'd have to pass that variable down through all of the components until we got to E. With context we could just provide this variable in context, and then consume context in the components that need it. It's often said not to do this as you can very easily make a lot of components aware of things they don't need to be.

A much better example would be if you have a global theme, as every component would need to be aware of this theme in order to perform its own styling.

How?

class A extends Component {
  getChildContext() {
    return {
      theme: "green"
    };
  }

  render() {
    return <D />;
  }
}

class E extends Component {
  render() {
    return (
      <div style={{ color: this.context.theme }}>
        {this.children}
      </div>
    );
  }
}

Here's a full example of what we mentioned above to show how context is used.

Simple Provider

Now that we know how context works, let's use it to provide our app with a theme.

class ThemeProvider extends Component {
  getChildContext() {
    return { color: this.props.color }
  }

  render() {
    return <div>{this.props.children}</div>
  }
}

ThemeProvider.childContextTypes = {
  color: PropTypes.string
}

// Our main file somewhere
ReactDOM.render(
  <ThemeProvider color="green">
    <App />
  </ThemeProvider>,
  document.getElementById('app')
)

class Paragraph extends React.Component {
  render() {
    const { color } = this.context

    return <p style={{ color: color }}>{this.props.children}</p>
  }
}

Paragraph.contextTypes = {
  color: PropTypes.string
}

Now we have a ThemeProvider that takes in a single prop, and sets that prop as context throughout the whole app. If you've used any state management libraries this should all be looking very familiar. In any child component of <App /> we can now access this.context.color just as you would with any this.props or this.state. You may now know what's next, how do we turn this into a more friendly HOC than using this.context everywhere?

HOC Provider

const getContext = contextTypes => Component => {
  class GetContext extends React.Component {
    render() {
      return <Component { ...this.props } { ...this.context } />
    }
  }

  GetContext.contextTypes = contextTypes

  return GetContext
}

This will work exactly the same as any other library. The main purpose of this is to try and reduce the amount of properties being sent into our child component. If, for example, we have 100s of properties stored in context, then we don't want to send all of them down to every component.

const Heading = ({ color, children }) => <h1 style={{ color }}>{children}</h1>

const contextTypes = {
  color: PropTypes.string
}

const HeadingWithContext = getContext(contextTypes)(Heading);

Just as we do with Redux's connect function, we can create an Object defining what we want from context, and our getContext helper will fetch it and add it as props to the component. Great!

Using Recompose to Create a Provider

recompose can be used to create the above context HOC in a much cleaner fashion:

const Provider = () => withContext(
  { store: PropTypes.object },
  props => ({ store: props })
)(props => React.Children.only(props.children));

const connect = Component => getContext(
  { store: PropTypes.object }
)(Component);

export { Provider, connect };

Much simpler. Our Provider will work exactly the same, but our getContext helper can be refactored into a much cleaner connect helper. It uses getContext to deconstruct the context Object and get context.store, and then pass that in as props for the component.

How to use it?

const App = () => <Home />

const Home = connect(({ store }) => <h1>{store.app.title}</h1>)

render(
  <Provider app={{ title: 'recompose is cool' }}>
    <App />
  </Provider>,
  document.getElementById('app')
)

Just like that, we have virtually implemented react-redux. In fact, if we really did want to implement react-redux we'd only have to change our connect helper to the following for it to work:

const connect = mapStateToProps => Component => compose(
  getContext({ store: PropTypes.object }),
  mapProps(props => ({ ...props, ...mapStateToProps(props.store) })),
)(Component);

// Usage
const mapStateToProps = store => ({
  title: store.app.title
})

export default connect(mapStateToProps)(Home)

Now, our connect helper will first get the context, and then use our mapStateToProps function to map the required props to the component. It's not perfect, as it currently requires a mapStateToProps function to be passed in, but the basic idea is there at least.

Suspense

// Suspens lets your components "wait" for something before rendering
const resource = fetchProfileData();

function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline />
      </Suspense>
    </Suspense>
  );
}

function ProfileDetails() {
  // Try to read user info, although it might not have loaded yet
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline() {
  // Try to read posts, although they might not have loaded yet
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

// The implementation of fetchProfileData() is not that important, but it will return
// something like below:

return {
  read() {
    if (status === "pending") {
      throw suspender;
    } else if (status === "error") {
      throw result;
    } else if (status === "success") {
      return result;
    }
  }
};

Libraries like Relay will have their own integration with Suspense to work with this syntax. In the future, other libraries may have their own implementation. The point is that it throws a "suspender" when the resource is still reading, which under-the-hood will allow the component to continue rendering. Of course, the fallbacks will show where you add them, but it allows you to render "most" of the page before the data has even started fetching.

Suspense is not:

  • Data fetching implementation
  • Couple data fetching with your view layer. It helps display loading states in your UI, but it does not tie network logic to React components.

Suspense lets you:

  • Deeply integrate data fetching libraries with React. If a data fetching library is integrated with Suspense, using it in your components feels very natural.
  • Avoid race conditions. Avoid error-prone async code. Suspense "feels" like reading data synchronously, as if it was already loaded.

Traditional Approaches vs Suspense

Fetch-on-render (e.g. fetch in useEffect):

  • Start rendering components
  • Each component triggers a fetch in their useEffects
  • Often loeads to a waterfall effect of data fetching/loading
function ProfilePage() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then(u => setUser(u));
  }, []);

  if (user === null) {
    return <p>Loading profile...</p>;
  }
  return (
    <>
      <h1>{user.name}</h1>
      <ProfileTimeline />
    </>
  );
}

function ProfileTimeline() {
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    fetchPosts().then(p => setPosts(p));
  }, []);

  if (posts === null) {
    return <h2>Loading posts...</h2>;
  }
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

Running this code results in:

  • Start fetching user and show loading until a user has been returned
  • Once it's finished, this will trigger ProfileTimeline to render and start loading posts
  • Finally, both user and posts will be rendered

This is an unintentional waterfall sequence that could, and should have, been parallelized. As the app grows, this kind of behaviour could become more and more troublesome for the user.

Fetch-then-render

function fetchProfileData() {
  return Promise.all([
    fetchUser(),
    fetchPosts()
  ]).then(([user, posts]) => {
    return {user, posts};
  })
}

const promise = fetchProfileData();

function ProfilePage() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    promise.then(data => {
      setUser(data.user);
      setPosts(data.posts);
    });
  }, []);

  if (user === null) {
    return <p>Loading profile...</p>;
  }
  return (
    <>
      <h1>{user.name}</h1>
      <ProfileTimeline posts={posts} />
    </>
  );
}

// The child doesn't trigger fetching anymore
function ProfileTimeline({ posts }) {
  if (posts === null) {
    return <h2>Loading posts...</h2>;
  }
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

Similar to the last example, this waits until data has been fetched to render something for the user. Here, the API calls are grouped together, which would likely happen if you use GraphQL or any kind of request batching. We still wait, but the 2 API calls are ran in parallel.

Render-as-you-fetch (with Suspense)

Previously we wouuld start fetching, finish fetching and start rendering. The goal with Suspense is to start fetching, start rendering and then finish fetching. We want the UI to render as we are fetching data so the user sees something as soon as possible.

// This is not a Promise. It's a special object from our Suspense integration as in the 
// above simplified example.
const resource = fetchProfileData();

function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline />
      </Suspense>
    </Suspense>
  );
}

function ProfileDetails() {
  // Try to read user info, although it might not have loaded yet
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline() {
  // Try to read posts, although they might not have loaded yet
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}
  • As soon as ProfilePage renders, the fetching is kicked off and it tries to render both the ProfileDetails and the ProfileTimeline.
  • React will try to render ProfileDetails using "resource.user.read()". While the resource fetching, the library will throw a "suspender" and "suspend" the component.
  • Similarly, React will try to render ProfileTimeline and the same thing will happen when initially reading "resource.posts.read()".
  • Whenever a component is "suspended", it will try to use the "fallback" of the closest Suspense component above it in the tree.
  • Initially we will see "loading profile..." while ProfileDetails is "suspended"

Suspense and Race Conditions

function ProfilePage({ id }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(id).then(u => setUser(u));
  }, [id]);

  if (user === null) {
    return <p>Loading profile...</p>;
  }
  return (
    <>
      <h1>{user.name}</h1>
      <ProfileTimeline id={id} />
    </>
  );
}

function ProfileTimeline({ id }) {
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    fetchPosts(id).then(p => setPosts(p));
  }, [id]);

  if (posts === null) {
    return <h2>Loading posts...</h2>;
  }
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

Suppose that we have a button outside of the ProfilePage which will update the user ID that we want to show. Now, it's quite possible that we end up showing posts/profile data for two completely different users.

Solving it with Suspense:

const initialResource = fetchProfileData(0);

function App() {
  const [resource, setResource] = useState(initialResource);
  return (
    <>
      <button onClick={() => {
        const nextUserId = getNextId(resource.userId);
        setResource(fetchProfileData(nextUserId));
      }}>
        Next
      </button>
      <ProfilePage resource={resource} />
    </>
  );
}

function ProfilePage({ resource }) {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails resource={resource} />
      <Suspense fallback={<h1>Loading posts...</h1>}>
        <ProfileTimeline resource={resource} />
      </Suspense>
    </Suspense>
  );
}

function ProfileDetails({ resource }) {
  const user = resource.user.read();
  return <h1>{user.name}</h1>;
}

function ProfileTimeline({ resource }) {
  const posts = resource.posts.read();
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
  • We've moved the resource into the App component
  • When the user ID is updated, it passes down the new "resouce" to the children
  • As before, this new fetch will start straight away and the UI will also render straight way.