React routing and SPAs

SPAs or Single Page Applications, are applications that will not download the html markup from the server on every new link click. Instead, the application will use a component named Link that prevents link clicks from triggering a header location change.

Routing

Unlike with other frameworks like Angular, in React we do not have the concept of routing, because React is a simple lightweight library, and not a complete framework, it is only responsible for rendering the view; nothing more. So to add routing to our application, we need to install a library called react-router-dom.

How to make a component available from every other one

In index.js:

//...
import { BrowserRouter } from 'react-router-dom'
//...

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

registerServiceWorker();

By wrapping our main <App /> component with <BrowserRouter></BrowserRouter>, we are allowing BrowserRouter to pass properties to the App component (which is a child).

The Browser History object

BrowserRouter is a component wrapping the browser's history object. Which you normally access through:

window.history.back();
//or
window.history.go(-1);

window.history.forward();
//or
window.history.go(1);

// or to refresh the page:
window.history.go();

window.history.pushState({foo: 'bar'})

//etc.

Workflow of react-route-dom

To make use of react-route-dom package, we need to first wrap the App component with BrowserRouter in index.js. This could have been done in App.js but it makes more sense to return an App component in the App.js file instead of a wrapped app? This has the effect of making the history object available from everywhere down the App and children components.

Then we need to control what component is displayed based on the current url in the browser. This is done using routes. From App.js:

import {Route} from 'react-router-dom';
//...
class App extends Component {
  render() {
    return (
      <div>
        <Route path="/products" component={Products} />
      </div>
    );
  }
}

The Route component looks at the current url, and if the path matches, then it will render the specified component. It is like a simple if Statement. Note that if we put many routes, if many routs match, then every matching component will be rendered. You could limit this with the exact keyword

import {Route, Switch} from 'react-router-dom';
//...
class App extends Component {
  render() {
    return (
      <div>
        <Switch>
            <Route path="/products" component={Products} />
            <Route path="/cart" component={Cart} />
            <Route path="/" component={Home} />
        </Switch>
      </div>
    );
  }
}

The Switch component allows creating if elseif, ..., elseif, else expressions. That is why you should order routes from most specific to most general.

Then we need to give the user a way to go to the specified route. This is done through links in the application. We will thus create a Navigation component, containing all the links to the different routes in the App component.

2019 Additions to react

React Hooks

2020 additions to react

Concurrent mode

It allows the framework to handle multiple state updates at the same time.

It opens the door to a new builtin react component called Suspense. Suspense will wait for asynchronous activity to finish and its children, such as an api call to a database, or better yet, using the new dynamic imports feature to lazy load a react component at runtime:

import React, { Suspense } from 'react';

React.lazy(() => import('./UserProfile');

function App() {
  return (
    <Suspense fallback={ <Spinner /> }>
      <UserProfile />
    </Suspense>
  );
}

That means you can handle loading states declaratively and you don't need to manually toggle some kind of boolean value in your code.