Use package react-svg-radar-chart to create Radar chart ReactJS

Create a basic ReactJS App using the create-react-app Post here.

Our final aim is to generate a Radar Chart using the npmjs package react-svg-radar-chart. Also, the dot markings on the chart should be able to show the current index and key, values on the chart on Hover as shown below.

Install the Package in your Project by running the following command in the Terminal:

npm install react-svg-radar-chart

Replace the code for the index.js file as shown below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

Create the App.js file for the App component and add the below code:

import React, { Component } from "react";
import MyComp from "./MyComp";
import MyRadarComp from "./MyRadarComp";

export default class App extends Component {
    render() {
        return (
          <div className="App">
            <MyRadarComp />
          </div>
        );
      }
}

Create MyRadarComp.js file and add the below code:

import React from 'react';
 
import RadarChart from 'react-svg-radar-chart';
import 'react-svg-radar-chart/build/css/index.css'
 
export default class MyRadarComp extends React.Component {
  render() {
    const tooltipstyle = {
        position: 'relative',
        display: 'inline-block',
        borderBottom: '1px dotted black'
    }

    const tooltiptextstyle = {
        visibility: 'hidden',
        width: '220px',
        backgroundColor: 'black',
        color: '#fff',
        textAlign: 'center',
        borderRadius: '6px',
        padding: '5px 0',
    
        /* Position the tooltip */
        position: 'absolute',
        zIndex: '1',
    }

 	 const data = [
      {
        data: {
          battery: 0.5,
          design: .7,
          useful: 0.985,
          speed: 0.57,
          weight: 0.7
        },
        meta: { color: 'blue' }
      },
      {
        data: {
          battery: 0.6,
          design: .85,
          useful: 0.5,
          speed: 0.6,
          weight: 0.7
        },
        meta: { color: 'red' }
      },
      {
            data: {
              battery: 0.7,
              design: .8,
              useful: 0.9,
              speed: 0.67,
              weight: 0.8
            },
            meta: { color: '#58FCEC' }
        }
    ];
 
    const captions = {
      // columns
      battery: 'Battery Capacity',
      design: 'Design',
      useful: 'Usefulness',
      speed: 'Speed',
      weight: 'Weight'
    };

    const noSmoothing = points => {
        let d = 'M' + points[0][0].toFixed(4) + ',' + points[0][1].toFixed(4);
        for (let i = 1; i < points.length; i++) {
          d += 'L' + points[i][0].toFixed(4) + ',' + points[i][1].toFixed(4);
        }
        return d + 'z';
      };
       
      const defaultOptions = {
        size: 200,
        axes: true, // show axes?
        scales: 3, // show scale circles?
        captions: true, // show captions?
        captionMargin: 10,
        dots: true, // show dots?
        zoomDistance: 1.2, // where on the axes are the captions?
        setViewBox: (options) => `-${options.captionMargin} 0 ${options.size + options.captionMargin * 2} ${options.size}`, // custom viewBox ?
        smoothing: noSmoothing, // shape smoothing function
        axisProps: () => ({ className: 'axis' }),
        scaleProps: () => ({ className: 'scale', fill: 'none' }),
        shapeProps: () => ({ className: 'shape' }),
        captionProps: () => ({
          className: 'caption',
          textAnchor: 'middle',
          fontSize: 10,
          fontFamily: 'sans-serif'
        }),
        dotProps: () => ({
          className: 'dot',
          mouseEnter: (dot) => { 
              document.getElementById("tooltip").innerText = "index: " + dot.idx + ", key: " + dot.key + ", value: " + dot.value;
              document.getElementById("tooltip").style.visibility = "visible";
            },
          mouseLeave: (dot) => { 
              document.getElementById("tooltip").innerText = "";
              document.getElementById("tooltip").style.visibility = "hidden";
            }
        })
      };
 
    return (
      <div>
        <RadarChart
            captions={{
              // columns
              battery: 'Battery Capacity',
              design: 'Design',
              useful: 'Usefulness',
              speed: 'Speed',
              weight: 'Weight'
            }}
            data={data}
            size={400}
            options={defaultOptions}
          />
          <div id="divtool" style={tooltipstyle}><label id="tooltip" style={tooltiptextstyle}></label></div>
        </div>
    );
  }
}

Run the Project:

npm start

A few points about the MyRadarComp component:

  1. The code is taken from the npmjs package sample provided in the package description.
  2. I’ve modified the css a bit to show the dots on the chart and also show the values on the Hover event of the dots.
  3. The caption labels and the data property names should match in order to correctly display the data.

Setup Github Repo from Visual Studio Code

  1. Create the Project or directory on your file system. My local folder is called formik-sample. You can also create using the create-react-app Post here for a sample ReactJS App.
  2. Create a repository on Github. I’ve named it sample-form-reactjs.
  3. Click on Clone or Download. Copy the SSH link.
  4. Open the local folder in VS Code as created in Step 1.
  5. In the Terminal Window in VS Code, type the following commands: git config –global user.name pulkit.gemini@gmail.com git clone git@github.com:pulkitgulati/sample-form-reactjs.git
  6. After this is done, just follow the same steps for git stage/commit/push etc.

For # 3:

However, if Step 5. gives error like below:

Check your network firewall settings if you’re doing it from Office. Try it out from your home and it should work.

Using Fetch with React example async await

I’ve created a basic React App as described in my previous post. This is just a demo on how you can use fetch API to load data in the componentDidMount() lifecycle method with/without
async/await. I’ve not used any error handling yet.

The basic structure of the React App contains index.js and index.html files.

Replace the code of the index.js file as below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

Create App.js file under the src directory and add the below code:

import React, { Component } from "react";
import MyComp from "./MyComp";
export default class App extends Component {
    render() {
        return (
          <div className="App">
            <MyComp />
          </div>
        );
      }
}

It’s time to create MyComp which is the sample component that will call the placeholder JSON API to show the list of users using Fetch GET request.

The code for MyComp is as shown below:

import React from "react";
export default class MyComp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            usernames : []
        };
    }
    componentDidMount() {
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(res => res.json())
            .then(json => this.setState({ usernames: json }));
    }
    render() {
        return(
            <div>
                Hey guys!
                <ul>
                {this.state.usernames.map(user => (
                    <li key={user.username}>
                    Hello, {user.username}
                    </li>
                ))}
                </ul>
            </div>
        );
    }
}

Run the App using as below:

npm start

The above code is now using async/await. It is a clean asynchronous way to call the API by writing unblocking code just like promises and callbacks.

Run the following command in the terminal:

npm i @babel/preset-env @babel/plugin-transform-runtime @babel/runtime --save

Replace the componentDidMount() code as below:

async componentDidMount() {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users`);
        const json = await response.json();
        this.setState({ usernames: json });
    }

The async function which in this case is the componentDidMount contains the await expression that pauses the execution of the async function. It waits until the passed Promise is resolved. It then resumes the async function’s execution and evaluates as the resolved value.

Run the App again to see the results which are the same in this case.

How to use package save-svg-as-png to download png image

This post explains how to use the package save-svg-as-png to download the png image of the contents in your svg element in your React component.

Create a basic React App as previously discussed in this post.

Create a component ImageWrapper as shown below:

import React from 'react'


export default class ImageWrapper extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
    }
  }

  render() {
    return (
      <div>
          <svg id={"svg-chart"} viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
            <rect x="0" y="0" width="100%" height="100%"/>
            <circle cx="50%" cy="50%" r="2" fill="white"/>
          </svg>
      </div>
    )
  }
}

Install the following package in your App with the command:

npm install save-svg-as-png

Modify the code in your App.js file as shown below:

import React, { Component } from "react";
import "./App.css";
import ImageWrapper from "./components/ImageWrapper";

const saveSvgAsPng = require('save-svg-as-png')

const imageOptions = {
  scale: 5,
  encoderOptions: 1,
  backgroundColor: 'white',
}

class App extends Component {

  handleClick = () => {
    saveSvgAsPng.saveSvgAsPng(document.getElementById('svg-chart'), 'shapes.png', imageOptions);
  };

  render() {
    return (
      <div className="App">
        <ImageWrapper />
        <br />
        <button onClick={this.handleClick}>Download Image</button>
      </div>
    );
  }
}

export default App;

Run the App with:

npm start

Click on the button Download Image to test.

ReactJS Component life cycle

We can declare special methods on the component class to run some code when a component mounts and unmounts also called lifecycle methods.

A ReactJS component goes through the following phases: initialization, mounting, updating and unmounting.

constructor() – Not a lifecycle method exactly.

State should usually be assigned in the constructor as it is used to set the initial state of our component. You can directly set the state property inside the constructor as setState method is not available before the component
is mounted.

componentWillMount()

This was deprecated in post React 17.0. If you still continue using this, you should use UNSAFE_componentWillMount().

render()

Rendering happens when the component mounts or updates. It has to be pure i.e. it cannot modify the state using setState. It should only handle the rendering of your component to the UI.

componentDidMount()

You can make calls to a remote endpoint like an Web Service or API to load data and is the best place to do so. Using setSate here will cause state updates and cause another rendering that happens before the browser updates the UI.
States should usually be assigned in the constructor though as mentioned above.

componentDidUpdate()

This method is called in response to props or state changes. setState should only be used with caution as may lead to inifinite loop. You can wrap it in a condition as shown below:

componentDidUpdate(prevProps) {
 //Compare the props
 if (this.props.title !== prevProps.title) {
   //do something -- here we have compared the current props with the previous props before doing something.
 }
}

componentWillUnmount()

This is the end of the lifecycle before the component is unmounted and destroyed. This can be used for any cleanups like clearing storage caches.

Create a basic ReactJS App, to follow through the example. You can check this post to get started.

In the example below to check out how these Component methods are called to depict the life cycle, the output for all these methods are logged to the Console.

The constructor initializes the state of the Component. The render method is called after every ComponentWill methods. Once the Component is rendered to the DOM, the ComponentDid method is called.

The componentDidMount() method runs after the component output has been rendered to the DOM.

import React, { Component } from "react";

class TestComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      increment: this.props.increment
    };
    console.log(this.state.increment, "Initialized");
  }

  componentWillMount() {
    this.setState({
      increment: this.state.increment = parseInt(this.state.increment) + 1
    });
    console.log(this.state.increment, "Inside ComponentWillMount");
  }

  componentDidMount() {
    this.setState({
      increment: this.state.increment = parseInt(this.state.increment) + 1
    });
    console.log(this.state.increment, "Inside ComponentDidMount");
  }

  componentWillUnmount() {
    this.setState({
      increment: this.state.increment = parseInt(this.state.increment) + 1
    });
    console.log(this.state.increment, "Inside ComponentWillUnmount");
  }

  componentWillUpdate() {
    console.log(this.state.increment, "Inside ComponentWillUpdate");
  }

  componentDidUpdate() {
    console.log(this.state.increment, "Inside ComponentDidUpdate");
  }
  

  render() {
    console.log(this.state.increment, "Render method called");
    return (
      <React.Fragment>
        <div>{this.state.increment} Render method called!</div>
      </React.Fragment>
    );
  }
}

export default TestComponent;
Order of how the Methods were called
DOM output

Whenever the TestComponent component is removed from the DOM, React calls the componentWillUnmount() lifecycle method.

Redux with React JS

Understanding how redux works has been difficult for a beginner like me in general. Redux has no relation to React but works well with it. I’ve prepared a simple example to build on my other post that uses i18n package to internationalize your SPA using React JS.

This example maintains the state of my previously selected language in Redux store. You can find the completed code here in my Github profile.

Redux is quite an excellent State Managment Framework. I’m using the official react-redux package that takes care of a lot of stuff like one component that wraps your entire app also called higher-order component. This component will subscribe to your store. The rest of your components will be children to this wrapper component and will only get the parts of the state that they need.

You will then have at least one main component that listens to state changes passed down by the Provider from the store. You can specify which parts of the state it should listen to, and those pieces of the state will be passed down as props to that component (and then of course, it can pass those down to its own children). You can specify that by using the connect() function on your Parent level component and using the mapStateToPropsfunction as a first parameter.

The entire state of the Application is represented by a State tree which is read-only. And every time you need to modify the state, you need to dispatch an Action.

Action object structure should always have a type property. Any other custom property can be added.
Type property e.g. “INCREMENT”, “DECREMENT”

Pure functions are predictable. They always give the same output for the same set of arguments.
Impure functions have side-effects. They may make changes to the data with database or network calls.
Redux takes the previous state, dispatches an action and returns the next state as a new object. This function that describes the state mutation is called the reducer.
Reducers expect 2 arguments: state and action.

e.g.
const counter = (state=0, action) => {
	switch(action.type) {
		case 'INCREMENT':
			return state + 1;
		case 'DECREMENT':
			return state - 1;
		default:
			return state;
	}
}

The store binds together the 3 principles of Redux:

  • It holds the applications’s current state object.
  • It lets you dispatch actions.
  • When you create it, you need to specify the reducer that tells you how state is updated with actions.

Below is the code for the store initialization:

import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import { composeWithDevTools } from 'redux-devtools-extension';
import Immutable from 'immutable';
const persistConfig = {
  key: 'root',
  storage : storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer);
const initialState = {};
const middleware = [thunk];
const composeEnhancers = composeWithDevTools({
  serialize: {
    immutable: Immutable
  }
});
export const store = createStore(persistedReducer, initialState, composeEnhancers(applyMiddleware(...middleware)));
export const persistor = persistStore(store)

Below is the code for App Component wrapped under the Provider component:

import React, { Component, Suspense } from "react";
import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
import { Provider } from 'react-redux';
import { store, persistor } from '../store';
import { PersistGate } from 'redux-persist/integration/react'
import HomePage from "./home/HomePage";
import AboutPage from "./about/AboutPage";
import Header from "./common/Header";
import PageNotFound from "./PageNotFound";
import CoursesPage from "./courses/CoursesPage";
import '../config/i18n'
export class App extends Component {
  
  render() {
    let data = localStorage.getItem('persist:root') && JSON.parse(JSON.parse(localStorage.getItem('persist:root')).langOptionsReducer).option.value;
    console.log(data)
    console.log(this.props)
    this.props.props.changeLanguage(data)
    return (
      < Provider
        store={store} >
        {/* https://github.com/rt2zz/redux-persist/blob/master/docs/PersistGate.md */}
        < PersistGate
          loading={<div>loading app...</div>}
          // onBeforeLift={onBeforeLift}
          persistor={persistor} >
      <div className="container-fluid">
        <Header />
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route path="/courses" component={CoursesPage} />
          <Route path="/about" component={AboutPage} />
          <Route component={PageNotFound} />
        </Switch>
      </div>
      </PersistGate >
      </Provider >
    );
  }
}
export default App;

Below is the code for the action method, changeLang that is dispatched when a language option is selected:

export const changeLang = (option)=> (dispatch) => {
    dispatch({type : 'langOptions',
    payload : option})
}

This is the code for the langOptions reducer that is fired by the action method:

const initialState={option:{}}
export default function(state=initialState, action){
    switch (action.type) {
        case 'langOptions':
            return {
                ...state,
                option:action.payload
            }
        default:
            return state
    }
}

All the reducers in your application need to be combined into a single reducer as in the index.js file under the reducers folder:

import { combineReducers } from 'redux';
import langOptionsReducer from './langOptions'
export default combineReducers({
    langOptionsReducer
})

React Redux provides a connect function for you to read values from the Redux store (and re-read the values when the store updates). The connect function takes two arguments, both optional: mapStateToProps : called every time the store state changes. It receives the entire store state, and should return an object of data this component needs. 2nd parameter is mapDispatchToProps which can either be a function or a full object of action creators.

Code for the HomePage with connect function using mapStateToProps argument as below:

import React, { Component } from "react";
import { Link } from "react-router-dom";
//import "../../node_modules/bootstrap/dist/css/bootstrap.css";
import Select from "react-select";
import { options } from "../../config/options";
import "../../config/i18n";
import { withTranslation } from "react-i18next";
import { connect } from "react-redux";
import { changeLang } from "../../actions/langOptions";
export class HomePage extends Component {
  constructor(props) {
    super(props);
    console.log("inside constructor");
    this.state = {
      lang: options[0]
    };
  }
  changeLang = lang => {
    const { i18n } = this.props;
    const { value } = lang;
    this.props.changeLang(lang);
    this.setState({ lang });
    i18n.changeLanguage(value);
  };
  componentWillReceiveProps(props) {
    console.log(props);
  }
  componentDidMount() {
    this.setState({
      lang: this.props.option.value ? this.props.option : options[0]
    });
    // this.props.i18n.changeLanguage(options[2].value);
  }
  render() {
    const { lang } = this.state;
    return (
      <React.Fragment>
        <div className="jumbotron">
          <h1>Courses Administration</h1>
          <Link to="about" className="btn btn-primary btn-lg">
            Learn more
          </Link>
        </div>
        <br />
        <div style={{ width: "200px" }}>
          <Select
            defaultValue={options[0]}
            options={options}
            value={lang}
            onChange={this.changeLang}
            className="App-Select"
          />
        </div>
      </React.Fragment>
    );
  }
}
const mapStateToProps = state => {
  return {
    option: state.langOptionsReducer.option
  };
};
export default connect(
  mapStateToProps,
  { changeLang }
)(withTranslation("translations")(HomePage));

Internationalization with React JS i18n package

Building Single Page Applications (SPA) in React JS that require multi-language support require a way for the user to be able to select the preferred language and all or specific text on the page or site should change.

React supports a package i18next that helps load the default language and save the preferred language which the user selects from the UI. react-i18next is built on top of i18next and is a powerful internationalization framework for React JS. This post explains i18n usage for v10 or above.

This example is built on top of my other post for Client-side routing here.

You can find the i18n demo code in my Github profile here.

You need to install the package react-i18next using npm as below:

npm install i18next
npm install react-i18next

Initialize the i18next store placed under the config folder as below:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "../locales/en";
import fr from "../locales/fr";
import it from "../locales/it";
import de from "../locales/de";
import es from "../locales/es";
i18n.use(initReactI18next).init({
  resources: {
    en,
    fr,
    it,
    de,
    es
  },
  fallbackLng: "en",
  debug: true,
  ns: ["translations"],
  defaultNS: "translations",
  keySeparator: false,
  interpolation: {
    escapeValue: false,
    formatSeparator: ","
  },
  react: {
    wait: true
  }
});
export default i18n;

The i18n package has a provider which would wrap your react App component.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./components/App";
import * as serviceWorker from "./serviceWorker";
import { BrowserRouter as Router } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import i18n from "./config/i18n";
import "bootstrap/dist/css/bootstrap.min.css";
ReactDOM.render(
  <I18nextProvider i18n={i18n}>
    <Router>
      <App />
    </Router>
  </I18nextProvider>,
  document.getElementById("root")
);

Maintain your translation files under the Locales folder and make sure the namespace matches the one provided in the i18next store initialization.
Example translation here:

{
  "translations": {
    "title": "<0>Acerca de</0>",
    "intro": "<0>Esta es la página Acerca de.</0>"
  }
}

The text in the About page is translated as below using the locales:

import React from "react";
import { Trans } from "react-i18next";

const AboutPage = () => (
  <div>
    <Trans i18nKey="title">
      <h2>About</h2>
    </Trans>
    <Trans i18nKey="intro">
      <p>This is the About page.</p>
    </Trans>
  </div>
);

export default AboutPage;

The i18nKey matches the specific text to be translated under the namespace.

The HomePage.js code has a drop-down change language event that will change the translation in the About page when selected.

changeLang = lang => {
    const { i18n } = this.props;
    const { value } = lang;
    this.props.changeLang(lang);
    this.setState({ lang });
    i18n.changeLanguage(value);
  };

The app is hosted on IIS Server. Check out the post here on how to host react app on IIS.

There is also a branch in Github here, that explains the i18n legacy version usage i.e. till v9.