Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Developing modern offline apps with ReactJS, Redux and Electron – Part 2 – ReactJS Basics

6.11.2017 | 10 minutes of reading time

ReactJS Basics

In this part of our blog series on building an offline desktop app, we will talk about the UI framework ReactJS (aka React) that we chose for the project. We will talk about the core features of React, give you a couple of code examples and explain how to test and debug ReactJS applications.

  1. Introduction
  2. ReactJS
  3. ReactJS + Redux
  4. Electron framework
  5. ES5 vs. ES6 vs. TypeScript
  6. WebPack
  7. Build, test and release process

Introduction

For our project we needed a lightweight JavaScript UI framework that provides the opportunity to render and update thousands of components on a single page and still be performant. After evaluating different frameworks, we decided to use ReactJS. Key factors for choosing ReactJS were the Virtual DOM, component lifecycle methods and conditional rendering that allow us to create extremely efficient and complex pages.

For a detailed introduction to the ReactJS ecosystem, we recommend the following article from our colleague Holger: https://blog.codecentric.de/en/2016/01/road-testing-react-js/ .

React Core Features

Here is a summary of the core features. We will cover each feature in detail throughout the examples.

JSX (JavaScript Syntax Extension)

  1. JSX has a concise and familiar syntax for defining tree structures with attributes
  2. JSX syntax needs to be transpiled to JavaScript (i.e. by Babel)
  3. In contrast to JavaScript, JSX is statically-typed
  4. JSX uses ES6 classes, which are similar to Java

One-way data flow

  1. Properties are passed by the parent component to the child components
  2. The properties are treated as immutable values
  3. Child components should never directly modify any properties that are passed to them
  4. PropTypes can be used for defining the type of each property that is passed to a given component. If a component receives an invalid value for a property, there will be a warning in the console. PropTypes are checked during runtime

Virtual DOM

  1. React uses the Virtual DOM to create an in-memory copy of the browsers DOM
  2. When changing the state of components, React updates the virtual DOM first
  3. After that it computes the resulting differences, and updates the browser’s displayed DOM efficiently

Lifecycle methods

These methods can be overridden for each component, to run code during a specific lifecycle phase.

  1. Mounting:
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()
  2. Updating:
    • componentWillReceiveProps()
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render()
    • componentDidUpdate()
  3. Unmounting:
    • componentWillUnmount()
  4. Error handling (since React 16 – released September 2017):
    • componentDidCatch()

We will explain these core features in more detail with a couple of code snippets in the next section.

Babel (ES5, ES6)

The JavaScript syntax that you will see in our examples is ECMAScript 6 (ES6) notation. If you need to run your web app inside older web browsers, you can use Babel (https://babeljs.io/ ) to transpile ES6 code to ES5. Here is an example how babel will transpile the ES6 arrow function to a regular ES5 function.

ES6 syntaxTranspiled to ES5 syntax
[1,2,3,4].map(n => n + 1);




[1,2,3,4].map(
 function(n) {
  return n + 1; 
 }
);

React example snippets

Simple React Component

So what makes the React component model so great?

“Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.” (https://reactjs.org/ )

Let’s get started by writing a simple React application and our first component. To write a simple React component, you create an App class that extends React.Component. The App class has a render method that is called when the component is about to get rendered. It can also have a constructor where you can initialize the components state.

To integrate React into your HTML web app, you typically have a

with an id to which you attach the React component. To do so, you use the ReactDOM.render function.

The following table shows two examples on how to write React code. On the left hand side, you see React code written in JSX syntax. On the right hand side, JSX syntax transpiled to plain JavaScript. At first glance, it may seem a little strange to mix JS and HTML code. The HTML notation on the left hand side is not actual HTML – you can think of it as a short notation for the elements you create. But instead of writing React.createElement() all the time, it is much cleaner and shorter to use the JSX notation.

JSX (ES6)plain JavaScript (ES6)
class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         counter: 42
      }
   };
   render() {
      return (
         <div>
              Count: {this.state.counter}
         </div> 
     );
   }
}

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

class App extends React.Component {
    constructor(props) {
       super(props);
       this.state = {
          counter: 42
       };
    }
    render() {
       return React.createElement(
          'div',
          null,
          'Count: ',
          this.state.counter
       );
    }
 }
 
ReactDOM.render(
  React.createElement(App, null), 
  document.getElementById('container')
);

If you want to see this example in action and tweak it a bit, use this JSFiddle link: https://jsfiddle.net/xbtzz2zo/2/

Nested React Components / Reuse

We extend the previous example and create a Counter component that we will use in our app. The Counter component extends React.Component, and simply renders a title and the state of the counter prop variable. We use the Counter component in the app component by adding it inside the render function. In our example, we added two Counter components to show how easy it is to reuse components in different places of your application. The counter property is passed by the parent component to the child component using counter = {this.state.counter}. The child component can access the counter property via this.props.counter.

To change state variables of the App component, i.e. to increment the counter variable, you can use the setState() method of the React component. As method parameters you only need to provide the properties you want to change. In this example, we render a button, and when this button is clicked, we trigger the event handler increment(). To access properties, state and component methods from event handlers, you need to register and bind them explicitly to the component. The best place to bind your event handlers is the constructor.

JSX (ES6)plain JavaScript (ES6)
class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         counter: 0
      }
      this.increment =
         this.increment.bind(this);
   };

   increment() {
      this.setSate({
         counter:
            this.state.counter + 1
      });
   }

   render() {
      return (
         <div>
            <button
               onClick = {this.increment}>
               Increment
            </button>
            <Counter
               counter =
                  {this.state.counter}>
            </Counter>
            <Counter 
               counter =
                  {this.state.counter}>
            </Counter>
         </div>
      );
   }
}

class Counter extends React.Component {
   render() {
      return (
         <div>
            <h3>
               Count: {this.props.counter}
            </h3>
         </div>
      );
   }
}

Counter.propTypes = {
   counter: PropTypes.string,
};

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




class App extends React.Component {
    constructor(props) {
       super(props);
       this.state = {
          counter: 0
       };
       this.increment =
          this.increment.bind(this);
    }

    increment() {
       this.setState({
          counter:
             this.state.counter + 1 
       });
    }

    render() {
       return React.createElement(
          'div',
          null,
          React.createElement(
             'button',
             { onClick: this.increment },
             'Increment'
          ),
          React.createElement(
            Counter,
            { counter:
               this.state.counter }
          )
          React.createElement(
            Counter, 
            { counter:
               this.state.counter }
         )
       );
    }
 }
 
 class Counter extends React.Component {
    render() {
       return React.createElement(
          'div',
          null,
          React.createElement(
             'h3',
             null,
             'Count: ',
             this.props.counter
          )
       );
    }
}

Counter.propTypes = {
   counter: PropTypes.string,
};
 
ReactDOM.render(
   React.createElement(App, null),
   document.getElementById('container'));

JSFiddle link: https://jsfiddle.net/8pxafpyq/2/

Stateless Functional Components

As of React 14, you can use stateless functional components that are very useful when you have dumb components without any behavior. They have less boilerplate code and typically focus on the UI. The Counter component in our example only displays a counter variable that the parent component passes on to the child. Therefore, we do not need to extend the React.Component class and implement the render() function. With a stateless functional component, you can destructure all props in the function parameters and simply return the elements that should be rendered. You do not have to use the keyword this any more since you are not using a class. This makes stateless functional components easy to test, easy to understand, easy to reuse, and the code does not have any side effects. It is imported to note that stateless functional components do not have any lifecycle methods that standard React components implement. Once you need to use a lifecycle method, you need to convert your stateless functional component to a regular React component.

React ComponentStateless Functional Component
class Counter extends React.Component {
   render() {
      return (
         <div>
            <h3>
               Count: {this.props.counter}
            </h3>
         </div>
      );
   }
}
const counter = ({ counter }) => (
   <div>
        <h3>Count: {counter}</h3>
   </div>
);






PureComponent

All React Components, except stateless functional components, have a lifecycle method shouldComponentUpdate() to check if the state has changed and the component needs to be re-rendered. The default implementation always returns true to trigger a re-render of the component. PureComponents use shallowEqual() to check if props or the state have changed. As a developer you have to evaluate and measure which implementation works best for your use case. In a later example we will show how to override shouldComponentUpdate()lifecycle method to optimize the render process of the React application.

React ComponentReact PureComponent
class Counter extends React.Component {
   render() {
      return (
         <div>
            <h3>
               Count: {this.props.counter}
            </h3>
         </div>
      );
   }
}
class Counter extends React.PureComponent {
   render() {
      return (
         <div>
            <h3>
               Count: {this.props.counter}
            </h3>
         </div>
      );
   }
}

Lifecycle methods

React Components (except functional components) have lifecycle methods that can be used to change the behavior of components. One common use case in our project was implementing shouldComponentUpdate() to specify which prop changes should trigger a re-render of a component. We have a lot of components on our pages, and this helped us increase the overall app performance a lot.

Here is a list of all lifecycle methods and the order in which they are called. As of React 16 (release September 2017), there is an error handling lifecycle method that you can use to catch exceptions inside your components.

  • Mounting:
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()
  • Updating:
    • componentWillReceiveProps()
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render()
    • componentDidUpdate()
  • Unmounting:
    • componentWillUnmount()
  • Error handling (since React 16):
    • componentDidCatch()

The following code snippet prints all lifecycle events to the console log. When you run the JSFiddle example, you will see the following output. First the Counter component is mounted and rendered. Then we click the increment button and trigger a props change. This will trigger a re-render of the component. After 10 seconds, we unmount the component and the componentWillUnmount() method will be triggered.

class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         counter: 0
      }
      this.increment =
         this.increment.bind(this);
   };

   increment() {
      this.setState({
         counter:
            this.state.counter + 1
      });
   }

   render() {
      return (
         <div>
            <button onClick = {this.increment}>
               Increment
            </button>
            <Counter counter = {this.state.counter}>
            </Counter>
         </div>
      );
   }
}

class Counter extends React.Component {
   constructor(props) {
     super(props);
     console.log('constructor');
   }

   componentWillMount() {
      console.log('componentWillMount');
   }

   componentDidMount() {
      console.log('componentDidMount');
   }

   componentWillReceiveProps(newProps) {    
      console.log('componentWillReceiveProps',newProps);
   }

   shouldComponentUpdate(newProps, newState) {
      console.log('shouldComponentUpdate');
      return newProps !== this.props ? true : false;
   }

   componentWillUpdate(nextProps, nextState) {
      console.log('componentWillUpdate');
   }

   componentDidUpdate(prevProps, prevState) {
      console.log('componentDidUpdate');
   }

   componentWillUnmount() {
      console.log('componentWillUnmount');
   }

   render() {
      console.log('render');
      return (
         <div>
            <h3>{this.props.counter}</h3>
         </div>
      );
   }
}

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

setTimeout(() => {
   ReactDOM.unmountComponentAtNode(
      document.getElementById('container'));}, 10000);

JSFiddle link: https://jsfiddle.net/6yhtjydv/4/

Component Styling

In our project, we use Twitter Bootstrap as CSS framework, which provides us with a large set of CSS components. Bootstrap allows us to write responsive web pages with minimal effort. To create a theme that matches our customer’s CI/CD best, we had to do a little tweaking. To customize the theme, we first chose a theme that our customer liked best. In our case, the Cerulean theme from Bootswatch.com was a great starting point. We copied the Cerulean LESS variables and imported them into the Bootstrap Live Customizer website. On this website you can see all styled Bootstrap components and further customize all Bootstrap LESS variables. In our case we had to tweak the brand primary color as well as a couple of other base stylings. After that we downloaded the Bootstrap resources (LESS variables, CSS stylesheets) and integrated them into our application. This approach covered most of our components.

For custom components that required special styling, we used the following flexible approach. Each component can have a custom CSS file that is saved parallel to the component. In the example below, the Spinner component imports the Spinner.css file and uses the custom CSS properties.

Spinner.jsx

import React from 'react';
import PropTypes from 'prop-types';
import './Spinner.css';

const Spinner = ({ message }) => (
   <div className="background">
      <div className="spinner">
         <p className="spinner_text">
            {message}
         </p>
      </div>
   </div>
);

Spinner.propTypes = {
   message: PropTypes.string,
};

export default Spinner;

Spinner.css

.spinner {
   position: fixed;
   z-index: 1100;
   height: 10em;
   width: 10em;
   margin: auto;
   color: white;
}

.spinner_text {
   color: white;
   padding-top: 5px;
}

/* transparent overlay */
.background {
   z-index: 1400;
   background-color: rgba(0,0,0, 0.7);
   display: block;
   position: fixed;
   width: 100%;
   height: 100%;
}

share post

Likes

0

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.