How to build Todo List App in React using TypeScript

How to build Todo List App in React using TypeScript

This React and TypeScript tutorial explains how to build Todo List App in React using Typescript. Creating a Todo List App in React using Typescript. In this post, we'll be creating a Todo list app using TypeScript and React. If you're new to using TypeScript with React this is a great tutorial to get you started!

Creating a Todo List App in React using Typescript (Part 1) Creating a Todo List App in React using Typescript (Part 2) Creating a Todo List App in React using Typescript (Part 3)

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Getting Started with TypeScript and React - Why and How

Getting Started with TypeScript and React - Why and How

In this React Typescript tutorial, you'll understand why and how get started the right way with using Typescript with React, which will boost your productivity and reduce the amount of time wasted by the small run time errors

Get started the right way with using Typescript with React which will boost your productivity and reduce the amount of time wasted by small run time errors

Code: https://github.com/hidjou/classsed-react-typescript-tutorial

Data visualization in React using React D3

Data visualization in React using React D3

Curious about data visualization in React? Learn about data visualization in React using React D3. In this React tutorial, we will see how we can use React and D3 to create components that are both reusable and beautiful.

Data visualization refers to the technique used to communicate data in a more clear and efficient way using visual objects such as charts, graphs and many more.

On the web, there are many libraries that can be used to visualize data but one that stands out is the D3js library. It has become the de facto for data visualization and has won the confidence of many developers.

React is a library that is used by many developers. In this article, we will see how we can use React and D3 to create components that are both reusable and beautiful.

First, let’s examine each of these libraries briefly.

React

React is a JavaScript library for building user interfaces. It makes it easy to create reusable components that can be put together to form more complex components.

D3

D3.js is a JavaScript library for manipulating documents based on data. It uses HTML, CSS and SVG to bring data to life.

D3 seeks to provide a means for efficient data manipulation based on data rather than, providing frameworks to do every possible feature.

It is fast, supports large datasets and dynamic behaviors for animation and interaction.

Let’s see how we can use these two libraries together to create dynamic data visualization components.

Setting up React

The easiest way to set up react is by using the create-react-app boilerplate by React’s team.

To install it globally on your local machine so that it can be reused, run this in your terminal:

npm install -g create-react-app

Next, we create a new app using the create-react-app template:

create-react-app react-d3

Next, we change directory into the newly created project:

cd react-d3
Setting up D3

You can add the D3 library to your app either using the CDN or by installing via NPM.

In this case, we will be installing using NPM:

npm install d3

Now, we are all set to start using D3 to make data visualization in React.

To preview the app just created on your default browser, run the code below:

npm start
Building a bar chart with D3

Open the created project with your favorite text editor and navigate to src/App.js.

This is the component that is currently rendered in the browser. We would need to remove the content of the render() method so we can replace that with our own content.

In the src folder, create a new js file named BarChart.js. This is where we will build the bar chart that will be rendered.

To start, add the following code to the file:

import React, {Component} from 'react';
import * as d3 from "d3";

class BarChart extends Component {

}

export default BarChart;

We will use the ComponentDidMount lifecycle method to display the bar chart when the BarChart component has been mounted in the DOM.

Add the following to the BarChart component:

class BarChart extends Component {

  componentDidMount() {
    this.drawChart();
  }
}

The drawChart is the method where we will do all of our D3 magic.

Normally, when using D3 without React, you do not have to put your D3 code in a method, but this is important in React to ensure that the chart displays only when the component has been mounted on the DOM.

Next, we create the drawChart method:



drawChart() {
  
  const data = [12, 5, 6, 6, 9, 10];
    
  const svg = d3.select("body").append("svg").attr("width", 700).attr("height", 300);
  
}

What’s going on here?

First, we defined a variable data which contains the data we want to visualize.

Next, we defined an SVG using D3 methods. We are using SVG because it’s scalable that is, no matter how large the screen is or how much you zoom in to view the data, it will never appear pixelated.

The d3.select() is used to select an HTML element from the document. It selects the first element that matches the argument passed and creates a node for it.

In this case, we passed the body element, which we will change later to make the component more reusable.

The append() method appends an HTML node to the selected item and returns a handle to that node.

The attr method is used to add attributes to the element. This can be any attribute that you will normally add to the HTML element like class, height, width or fill .

We then appended an SVG element to the body element with a width: 700 and height: 300.

Under the SVG variable we created, add the following code:

svg.selectAll("rect").data(data).enter().append("rect")

Just like the select method, selectAll() selects the element that matches the argument that is passed to it. So, all elements that match the arguments are selected and not just the first.

Next, the data() method, is used to attach the data passed as an argument to the selected HTML elements.

Most times, these elements are not found because most visualization deal with dynamic data and it is nearly impossible to estimate the amount of data that will be represented.

The enter() method rescues us from that bottleneck as it is used alongside the append method to create the nodes that are missing and still visualize the data.

So far we have created nodes for each data point. All that’s left is to make it visible.

To make it visible we need to create a bar for each of those datasets, set a width and update the height of each bar dynamically.

The attr method allows us to use a callback function to deal with the dynamic data:

selection.attr("property", (d, i) => {})

Where d is the data point value and i is the index of the data point of the array.

First, we need to set each data point at a specific point on the x and y-axis of the bar chart. We use the “x” and “y” attributes to achieve this, where “x” represents the position of the bar along the x-axis(horizontally) and “y” represents the position of the bar along the y-axis.

Also, we need to set the width and height of each data point. The width of each data point is constant since the bars would be of the same width.

The height, on the other hand, depends on the value of each data point. We have to use the callback function to make the bar chart display the value of each data point.

We modify our SVG variable to become:

svg.selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", (d, i) => i * 70)
  .attr("y", 0)
  .attr("width", 25)
  .attr("height", (d, i) => d)
  .attr("fill", "green");

For the “x”, each index of the data point in the array is multiplied by a constant integer 70, to shift the position of each bar by 70.

“y” has a constant value y, which we will change soon.

The width also has a constant value of 65 which is less than the position of each element on the chart to create a space between each element.

The height of the bar depends on the value of each entry in the data set.

Using this we have created a bar chart. However, we have two issues:

  1. The bars in the chart are small
  2. The chart is also inverted

To resolve the above, we would multiply each data by a constant value of say 10, to increase the size of each bar without affecting the data:

.attr("height", (d, i) => d * 10)

Next, we solve the issue of the bar being inverted, but before that let’s understand why the chart is inverted in the first place.

The SVG position starts from top to bottom, so using a y attribute of 0 puts each bar at the top edge of the SVG element.

To fix this, we subtract the height of each bar from the height of the SVG element:

.attr("y", (d, i) => h - 10 * d)

Where (10 * d) is the height we got from our previous calculation.

Putting it all together, the BarChart component will be:



class BarChart extends Component {
  componentDidMount() {
    this.drawChart();
  }
    
  drawChart() {
    const data = [12, 5, 6, 6, 9, 10];
    
    const svg = d3.select("body")
    .append("svg")
    .attr("width", w)
    .attr("height", h)
    .style("margin-left", 100);
                  
    svg.selectAll("rect")
      .data(data)
      .enter()
      .append("rect")
      .attr("x", (d, i) => i * 70)
      .attr("y", (d, i) => h - 10 * d)
      .attr("width", 65)
      .attr("height", (d, i) => d * 10)
      .attr("fill", "green")
  }
        
  render(){
    return <div id={"#" + this.props.id}></div>
  }
}
    
export default BarChart;

We now have a basic bar chart. Let’s do a little extra and add labels.

Adding labels to a bar chart

To add labels we add the following code to the drawChart function:

svg.selectAll("text")
  .data(data)
  .enter()
  .append("text")
  .text((d) => d)
  .attr("x", (d, i) => i * 70)
  .attr("y", (d, i) => h - (10 * d) - 3)

This is similar to what we did for the bars but this time, text is appended instead.

The bar chart should now look like this:

Making the bar chart reusable

One of the important parts of React is to make components that are reusable.

To do this, we need to remove the provided data and then pass it to the component through props.

The width and height of the SVG will also be passed via props:

const data = [12, 5, 6, 6, 9, 10];

becomes

const data = this.props.data;

and the width and height attribute change from:

const svg = d3.select("body").append("svg").attr("width", 700).attr("height", 300);

to



const svg = d3.select("body").append("svg")
  .attr("width", this.props.width)
  .attr("height", this.props.height);

In our App.js file, we can now use the component and pass the data we want from the parent component.

class App extends Component {
  
  state = {
    data: [12, 5, 6, 6, 9, 10],
    width: 700,
    height: 500,
    id: root
  }

  render() {
    return (
      <div className="App">
        <BarChart data={this.state.data} width={this.state.width} height={this.state.height} />
      </div>
    );
  }
}

This way, we can reuse the bar chart anywhere we want to in our React App.

Cheers!!!

Mapbox Marker Clustering in React

Mapbox Marker Clustering in React

Mapbox Marker Clustering in React. In this tutorial, we will load remote data using SWR, cluster the markers together with Supercluster and the useSupercluster hook, and render it in React using Mapbox.

Performance can begin to degrade pretty quickly when you are trying to show large amounts of data on a map. Even at hundreds of markers using Mapbox via react-map-gl, you may feel it start to lag. By clustering the points together you can improve performance greatly, all while presenting the data in a more approachable way.

Supercluster is the go-to package for clustering points together on a map. For using supercluster together with React I created a useSupercluster hook to make things easier. This article shows how to integrate clustering with supercluster into your React Mapbox map.

Mapbox setup in React

Before fetching data to display, before clustering that data to display on the map, we need to set Mapbox up. I have an intro to Mapbox video if you haven't worked with the react-map-gl package before.

Mapbox in React requires you to manage Mapbox's viewport in state. This is where we can set initial values which are later updated via the onViewportChange event.

We will also create a mapRef variable to store a reference to the map itself. This is required in order to call functions on the map, in our case to get the bounding box of the map.

When developing this locally, I am storing my Mapbox token in a file called .env.local, and by naming it with the prefix REACT_APP_, it will get loaded into the app automatically by create react app.

export default function App() {
  // setup map
  const [viewport, setViewport] = useState({
    latitude: 52.6376,
    longitude: -1.135171,
    width: "100vw",
    height: "100vh",
    zoom: 12
  });
  const mapRef = useRef();

  // load and prepare data
  // get map bounds
  // get clusters

  // return map
  return (
    <ReactMapGL
      {...viewport}
      maxZoom={20}
      mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
      onViewportChange={newViewport => {
        setViewport({ ...newViewport });
      }}
      ref={mapRef}
    >
      {/* markers here */}
    </ReactMapGL>
  );
}

Preparing data for supercluster

Data from an external/remote source will most likely need to be massaged into the format required by the supercluster library. The example below uses SWR to fetch remote data using hooks.

We must produce an array of GeoJSON Feature objects, with the geometry of each object being a GeoJSON Point.

An example of the data looks like:

[
  {
    "type": "Feature",
    "properties": {
      "cluster": false,
      "crimeId": 78212911,
      "category": "anti-social-behaviour"
    },
    "geometry": { "type": "Point", "coordinates": [-1.135171, 52.6376] }
  }
]

Fetching the data using SWR and converting it into the proper format looks like:

const fetcher = (...args) => fetch(...args).then(response => response.json());

export default function App() {
  // setup map

  // load and prepare data
  const url =
    "https://data.police.uk/api/crimes-street/all-crime?lat=52.629729&lng=-1.131592&date=2019-10";
  const { data, error } = useSwr(url, { fetcher });
  const crimes = data && !error ? data : [];
  const points = crimes.map(crime => ({
    type: "Feature",
    properties: { cluster: false, crimeId: crime.id, category: crime.category },
    geometry: {
      type: "Point",
      coordinates: [
        parseFloat(crime.location.longitude),
        parseFloat(crime.location.latitude)
      ]
    }
  }));

  // get map bounds
  // get clusters
  // return map
}
Getting map bounds

For supercluster to return the clusters based on the array of points we created in the previous section, we need to provide it with two additional pieces of information:

  • The map bounds: [westLng, southLat, eastLng, northLat]
  • The map zoom: In Mapbox this will come from our viewport.zoom state

The bounds can be gathered by accessing the mapRef.current property that we set up at the beginning. By stringing a few function calls together we can get the data and place it into the correct format.

export default function App() {
  // setup map
  // load and prepare data

  // get map bounds
  const bounds = mapRef.current
    ? mapRef.current
        .getMap()
        .getBounds()
        .toArray()
        .flat()
    : null;

  // get clusters
  // return map
}
Fetching clusters from hook

With our points in the correct order, and with the bounds and zoom accessible, it's time to retrieve the clusters. This will use the useSupercluster hook provided by the use-supercluster package.

It returns you through a destructured object an array of clusters and, if you need it, the supercluster instance variable.

export default function App() {
  // setup map
  // load and prepare data
  // get map bounds

  // get clusters
  const { clusters, supercluster } = useSupercluster({
    points,
    bounds,
    zoom: viewport.zoom,
    options: { radius: 75, maxZoom: 20 }
  });

  // return map
}

Clusters are an array of GeoJSON Feature objects, but some of them represent a cluster of points, and some represent individual points that you created above. A lot of it depends on your zoom level and how many points would be within a specific radius. When the number of points gets small enough, supercluster gives us individual points rather than clusters. The example below has a cluster (as denoted by the properties on it) and an individual point (which in our case represents a crime).

[
  {
    "type": "Feature",
    "id": 1461,
    "properties": {
      "cluster": true,
      "cluster_id": 1461,
      "point_count": 857,
      "point_count_abbreviated": 857
    },
    "geometry": {
      "type": "Point",
      "coordinates": [-1.132138301050194, 52.63486758501364]
    }
  },
  {
    "type": "Feature",
    "properties": {
      "cluster": false,
      "crimeId": 78212911,
      "category": "anti-social-behaviour"
    },
    "geometry": { "type": "Point", "coordinates": [-1.135171, 52.6376] }
  }
]
Displaying clusters as markers

Because the clusters array contains features which represent either a cluster or an individual point, we have to handle that while mapping them. Either way, they both have coordinates, and we can use the cluster property to determine which is which.

Styling the clusters is up to you, I have some simple styles applied to each of the markers:

.cluster-marker {
  color: #fff;
  background: #1978c8;
  border-radius: 50%;
  padding: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.crime-marker {
  background: none;
  border: none;
}

.crime-marker img {
  width: 25px;
}

Then as I am mapping the clusters, I change the size of the cluster with a calculation based on how many points the cluster contains: ${10 + (pointCount / points.length) * 20}px.

export default function App() {
  // setup map
  // load and prepare data
  // get map bounds
  // get clusters

  // return map
  return (
    <ReactMapGL>
      {clusters.map(cluster => {
        // every cluster point has coordinates
        const [longitude, latitude] = cluster.geometry.coordinates;
        // the point may be either a cluster or a crime point
        const {
          cluster: isCluster,
          point_count: pointCount
        } = cluster.properties;

        // we have a cluster to render
        if (isCluster) {
          return (
            <Marker
              key={`cluster-${cluster.id}`}
              latitude={latitude}
              longitude={longitude}
            >
              <div
                className="cluster-marker"
                style={{
                  width: `${10 + (pointCount / points.length) * 20}px`,
                  height: `${10 + (pointCount / points.length) * 20}px`
                }}
              >
                {pointCount}
              </div>
            </Marker>
          );
        }

        // we have a single point (crime) to render
        return (
          <Marker
            key={`crime-${cluster.properties.crimeId}`}
            latitude={latitude}
            longitude={longitude}
          >
            <button className="crime-marker">
              <img src="/custody.svg" alt="crime doesn't pay" />
            </button>
          </Marker>
        );
      })}
    </ReactMapGL>
  );
}
Animated zoom transition into a cluster

We can always zoom into the map ourselves, but supercluster provides a function called getClusterExpansionZoom, which when passed a cluster ID, it will return us which zoom level we need to change the map to in order for the cluster to be broken down into additional smaller clusters, or individual points.

const expansionZoom = Math.min(
  supercluster.getClusterExpansionZoom(cluster.id),
  20
);

With the next zoom level provided to us by supercluster, we could simple update our Mapbox viewport state, but it wouldn't be a smooth transition. react-map-gl provides a class called FlyToInterpolator which animates the map to the new zoom and lat/lon rather than the change being instant.

setViewport({
  ...viewport,
  latitude,
  longitude,
  zoom: expansionZoom,
  transitionInterpolator: new FlyToInterpolator({
    speed: 2
  }),
  transitionDuration: "auto"
});

Where do the snippets of code above live? I have put them inside of an onClick event on the Marker's div for each cluster being rendered.

<Marker key={`cluster-${cluster.id}`} latitude={latitude} longitude={longitude}>
  <div
    className="cluster-marker"
    style={{
      width: `${10 + (pointCount / points.length) * 20}px`,
      height: `${10 + (pointCount / points.length) * 20}px`
    }}
    onClick={() => {
      const expansionZoom = Math.min(
        supercluster.getClusterExpansionZoom(cluster.id),
        20
      );

      setViewport({
        ...viewport,
        latitude,
        longitude,
        zoom: expansionZoom,
        transitionInterpolator: new FlyToInterpolator({
          speed: 2
        }),
        transitionDuration: "auto"
      });
    }}
  >
    {pointCount}
  </div>
</Marker>
Conclusion

Using react-map-gl, we have the ability to use Mapbox within our React app. Using use-supercluster we are able to use supercluster as a hook to render clusters of points onto our map.

Because we have access to the instance of supercluster, we're even able to grab the "leaves" (the individual points which make up a cluster) via the supercluster.getLeaves(cluster.id) function. With this we can show details about the x number of points contained within a cluster.

Full source code of this project can be found here.