Categories
JavaScript JavaScript Basics

Cloning Arrays in JavaScript

There are a few ways to clone an array in JavaScript,

Object.assign

Object.assign allows us to make a shallow copy of any kind of object including arrays.

For example:

const a = [1,2,3];
const b = Object.assign([], a); // [1,2,3]

Array.slice

The Array.slice function returns a copy of the original array.

For example:

const a = [1,2,3];
const b = a.slice(0); // [1,2,3]

Array.from

The Array.slice function returns a copy of the original array. It takes array like objects like Set and it also takes an array as an argument.

const a = [1,2,3];
const b = Array.from(a); // [1,2,3]

Spread Operator

The fastest way to copy an array, which is available with ES6 or later, is the spread operator.

const a = [1,2,3];
const b = [...a]; // [1,2,3]

JSON.parse and JSON.stringify

This allows for deep copy of an array and only works if the objects in the array are plain objects. It can be used like this:

const a = [1,2,3];
const b = JSON.parse(JSON.stringify(a)); // [1,2,3]

Categories
CSS

Simple Introduction to CSS Grid

CSS grid lets us create layouts with CSS easily.

It’s also good for creating responsive layouts because we can group different items together.

In this article, we’ll look at how to define a grid layout also make it responsive.

Defining a Grid Layout

We can define a grid layout with some HTML and CSS.

If we want to define 4 div’s with one header, one left and right div with an empty space in between it, and one footer, we can do it as follows.

First, we add the HTML:

<div class='container'>  
  <div class='item-a'>  
    A  
  </div>  
  <div class='item-b'>  
    B  
  </div>  
  <div class='item-c'>  
    C  
  </div>  
  <div class='item-d'>  
    D  
  </div>  
</div>

Then we add the CSS:

.item-a {  
  grid-area: header;  
  background-color: lightyellow;  
}

.item-b {  
  grid-area: main;  
  background-color: lightgreen;  
}

.item-c {  
  grid-area: sidebar;  
  background-color: lightblue;  
}

.item-d {  
  grid-area: footer;  
  background-color: pink;  
}

.container {  
  display: grid;  
  grid-template-columns: 24vw 25vw 25vw 24vw;  
  grid-template-rows: auto;  
  grid-template-areas:  
    "header header header header"  
    "main main . sidebar"  
    "footer footer footer footer";  
}

Then we get the following:

The CSS has the following parts.

First, we look at the container class. This is the most important part of the grid.

We have display: grid; which designates that the div with the container class has a grid layout.

Then we define the grid columns with the grid-template-columns , which we set the value to 24vw 25vw 25vw 24vw .

This means we have 4 columns with the leftmost and rightmost columns having 24vw and the one in between having 25vw .

Then we have grid-template-rows: auto; . We leave it as auto since we aren’t concerned with the height of the rows.

To define how the grid is shared between the components inside the div with the container class, we write:

grid-template-areas:  
    "header header header header"  
    "main main . sidebar"  
    "footer footer footer footer";

We have the first row all fille with the header area in the first row.

Then in the new row, we have the main area fill the 2 leftmost columns. A dot for an empty space, and the sidebar area for the rightmost column.

In the bottom row, we have the footer area fill the whole row.

Then we can designate the areas with the item classes as follows:

.item-a {  
  grid-area: header;  
  background-color: lightyellow;  
}

.item-b {  
  grid-area: main;  
  background-color: lightgreen;  
}

.item-c {  
  grid-area: sidebar;  
  background-color: lightblue;  
}

.item-d {  
  grid-area: footer;  
  background-color: pink;  
}

In the code above, we designated whatever having the item-a class as the header with the grid-area and set the background color.

This means that whatever has the class item-a will be the header that fills the top row.

Then we do the same with the other 3 classes, so whatever has the class item-b will fill the leftmost 2 columns in the second row. The element with the class item-c fills the rightmost column of the second row. The element with the class item-d fills all of the bottom rows.

This is how we define a layout with a grid.

Responsive Layout

We can define a responsive layout with CSS selectors and adding a new layout for narrow screens.

We can define a narrow screen layout that only shows the header, main and footer areas as follows:

.item-a {  
  grid-area: header;  
  background-color: lightyellow;  
}

.item-b {  
  grid-area: main;  
  background-color: lightgreen;  
}

.item-c {  
  grid-area: sidebar;  
  background-color: lightblue;  
}

.item-d {  
  grid-area: footer;  
  background-color: pink;  
}

.container {  
  display: grid;  
  grid-template-rows: auto;  
}

@media only screen and (min-width: 751px) {  
  .container {  
    grid-template-columns: 24vw 25vw 25vw 24vw;  
    grid-template-areas:  
      "header header header header"  
      "main main . sidebar"  
      "footer footer footer footer";  
  }  
}

@media only screen and (max-width: 750px) {  
  .item-c {  
    display: none;  
  }

  .container {  
    grid-template-columns: 90vw;  
    grid-template-rows: auto;  
    grid-template-areas:  
      "header"  
      "main"  
      "footer";  
  }  
}

All we did is hide anything with class item-c when the screen width is 750px or less. Otherwise, we keep the same layout as before.

Otherwise, we just move the code around a bit to prevent duplication.

Then when our screen is narrow, we get

Conclusion

Using the CSS grid, making layouts is easier than ever.

First, we set the container div to display: grid to make it a container for a grid.

We then can define layouts with the grid-template-columns to define the columns, grid-template-rows to define the rows. This will form a grid.

Then we set the grid-template-areas attribute to define our layout one row at a time.

We can extend this to be responsive by using CSS media queries and then defining alternative layouts for different screen sizes.

Categories
JavaScript Vue

Generate Static Websites with Nuxt

Static websites are getting popular again nowadays. Informational and brochure sites no longer need to use content management systems like WordPress to be updated dynamically.

With static site generators, you can get your content from dynamic sources like headless CMS’s, APIs, and also from files like Markdown files.

Nuxt is a great static site generator based on Vue.js that is easy to use to build static websites. With Nuxt, all we have to do to build static websites from dynamic content is that we create the templates for showing the content dynamically from the dynamic sources like APIs and Markdown files. Then in the Nuxt configuration file, we define the routes statically so that it can go through the same routes to generate the content into static files.

In this article, we will build a news website using Nuxt and will use the News API, located at https://newsapi.org/, for the content. You have to know Vue.js before you can build a website using Nuxt since Nuxt is a framework based on Vue.js.

To start, first we register for an API key at the News API website. It is free if we only want the headlines. We start building the website by using the Nuxt CLI. We run the create-nuxt-app command by typing in:

npx create-nuxt-app news-website

This will create the initial project files in the news-website folder. When the wizard is run, we select none for server side frameworks, none for UI framework, none for test framework, Universal for the Nuxt mode, and choose to include Axios, linting and prettifying choices are up to you.

Next we need to install some packages. We need the @nuxtjs/dotenv package for reading the environment variables locally and the country-list library for getting a list of countries in our website. To install them we run:

npm i @nuxtjs/dotenv country-list

Now we can start building our website. In the default.vue file, we replace the existing code with:

<template>  
  <div>  
    <nav class="navbar navbar-expand-lg navbar-light bg-light">  
      <nuxt-link class="navbar-brand" to="/">News Website</nuxt-link>  
      <button  
        class="navbar-toggler"  
        type="button"  
        data-toggle="collapse"  
        data-target="#navbarSupportedContent"  
        aria-controls="navbarSupportedContent"  
        aria-expanded="false"  
        aria-label="Toggle navigation"  
      >  
        <span class="navbar-toggler-icon"></span>  
      </button> <div class="collapse navbar-collapse" id="navbarSupportedContent">  
        <ul class="navbar-nav mr-auto">  
          <li class="nav-item active">  
            <nuxt-link class="nav-link" to="/">Home</nuxt-link>  
          </li>  
          <li class="nav-item dropdown">  
            <a  
              class="nav-link dropdown-toggle"  
              href="#"  
              id="navbarDropdown"  
              role="button"  
              data-toggle="dropdown"  
              aria-haspopup="true"  
              aria-expanded="false"  
            >Headliny by Country</a>  
            <div class="dropdown-menu" aria-labelledby="navbarDropdown">  
              <nuxt-link  
                class="dropdown-item"  
                :to="`/headlines/${c.code}`"  
                v-for="(c, i) of countries"  
                :key="i"  
              >{{c.name}}</nuxt-link>  
            </div>  
          </li>  
        </ul>  
      </div>  
    </nav>  
    <nuxt />  
  </div>  
</template>

<script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      countries: getData()  
    };  
  }  
};  
</script>

<style>  
.bg-light {  
  background-color: lightcoral !important;  
}  
</style>

This is the file for defining the layout of our website. We added the Bootstrap navigation bar here. The bar has links for the home page and a drop-down for the list of countries. The nuxt-link components are all links to pages for getting the headlines for the country when the static files are generated. The countries are obtained from the country-list package in the script section by calling the getData function. In the style section, we changed the background color of our navigation bar by overriding the default color of the .bg-light class. The nuxt component in the bottom of the template section is where our content will be displayed.

Next we create amixins folder and create a file called requestsMixin.js file. In there, we add:

const APIURL = "https://newsapi.org/v2";  
const axios = require("axios");

export const requestsMixin = {  
  methods: {  
    getHeadlines(country) {  
      return axios.get(  
        `${APIURL}/top-headlines?country=${country}&apiKey=${process.env.VUE_APP_APIKEY}`  
      );  
    }, 

    getEverything(keyword) {  
      return axios.get(  
        `${APIURL}/everything?q=${keyword}&apiKey=${process.env.VUE_APP_APIKEY}`  
      );  
    }  
  }  
};

This file has the code to get the headlines by country and keyword from the News API.

Then in the pages folder, we create the headlines folder and in the headlines folder, create a _countryCode.vue file. In the file, we add:

<template>  
  <div class="container">  
    <h1 class="text-center">Headlines in {{getCountryName()}}</h1>  
    <div v-if="headlines.length > 0">  
      <div class="card" v-for="(h, i) of headlines" :key="i">  
        <div class="card-body">  
          <h5 class="card-title">{{h.title}}</h5>  
          <p class="card-text">{{h.content}}</p>  
          <button class="btn btn-primary" :href="h.url" target="_blank" variant="primary">Read</button>  
        </div>  
        <img :src="h.urlToImage" class="card-img-bottom" />  
      </div>  
    </div>  
    <div v-else>  
      <h2 class="text-center">No headlines found.</h2>  
    </div>  
  </div>  
</template><script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      headlines: [],  
      countries: getData()  
    };  
  },  
  beforeMount() {  
    this.getHeadlinesByCountry();  
  },  
  methods: {  
    async getHeadlinesByCountry() {  
      this.country = this.$route.params.countryCode;  
      const { data } = await this.getHeadlines(this.country);  
      this.headlines = data.articles;  
    }, 

    getCountryName() {  
      const country = this.countries.find(  
        c => c.code == this.$route.params.countryCode  
      );  
      return country ? country.name : "";  
    }  
  }  
};  
</script>

In the file, we accept the route parameter countryCode and from there, we call the this.getHeadlines function from the requestsMixin that we made earlier and included in this component to get the headlines from the News API. Then the results are displayed in Bootstrap cards in the template section. In the template, we get the country name by finding it from the country-list data. We display a message if there are no headlines found. In general, if we want to make a page that accepts URL parameters, we have to make a file with an underscore as the first character and the variable name of the URL parameter that we want. So _countryCode.vue will let us get the countryCode parameter by using this.$route.params.countryCode in this example.

Next in index.vue in the pages folder, we replace the exist code with:

<template>  
  <div class="container">  
    <h1 class="text-center">Home</h1>  
    <div class="card" v-for="(h, i) of headlines" :key="i">  
      <div class="card-body">  
        <h5 class="card-title">{{h.title}}</h5>  
        <p class="card-text">{{h.content}}</p>  
        <button class="btn btn-primary" :href="h.url" target="_blank" variant="primary">Read</button>  
      </div>  
      <img :src="h.urlToImage" class="card-img-bottom" />  
    </div>  
  </div>  
</template>

<script>  
import { requestsMixin } from "~/mixins/requestsMixin";  
const { getData } = require("country-list");

export default {  
  mixins: [requestsMixin],  
  data() {  
    return {  
      headlines: []  
    };  
  },  
  beforeMount() {  
    this.getHeadlinesByCountry();  
  },  
  methods: {  
    async getHeadlinesByCountry() {  
      const { data } = await this.getHeadlines("us");  
      this.headlines = data.articles;  
    }  
  }  
};  
</script>

<style>  
</style>

This lets us display the headlines for the US in the home page. It works similarly to the _countryCode.vue page except that we only get the US headlines instead of accepting a URL parameter to get headlines from different countries depending on the URL.

Next we create an create-env.js in the project’s root folder and add the following:

const fs = require('fs')  
fs.writeFileSync('./.env', `API_KEY=${process.env.API_KEY}`)

This allows us to deploy to Netlify because we need to create the .env file on the fly there from the entered environment variables. Also, we create the .env file manually and put the API_KEY as the key and the News API API key as the value.

Next in nuxt.config.js , we replace the existing code with:

require("dotenv").config();  
const { getData } = require("country-list");

export default {  
  mode: "universal",  
  /*  
   ** Headers of the page  
   */  
  head: {  
    title: "News Website",  
    meta: [  
      { charset: "utf-8" },  
      { name: "viewport", content: "width=device-width, initial-scale=1" },  
      {  
        hid: "description",  
        name: "description",  
        content: process.env.npm_package_description || ""  
      }  
    ],  
    link: [  
      { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },  
      {  
        rel: "stylesheet",  
        href:  
         "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"  
      }  
    ],  
    script: [  
      { src: "https://code.jquery.com/jquery-3.3.1.slim.min.js" },  
      {  
        src:  
          "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"  
      },  
      {  
        src:  
          "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"  
      }  
    ]  
  },  
  /*  
   ** Customize the progress-bar color  
   */  
  loading: { color: "#fff" },  
  /*  
   ** Global CSS  
   */  
  css: [],  
  /*  
   ** Plugins to load before mounting the App  
   */  
  plugins: [],  
  /*  
   ** Nuxt.js dev-modules  
   */  
  buildModules: [],  
  /*  
   ** Nuxt.js modules  
   */  
  modules: [  
    // Doc: https://axios.nuxtjs.org/usage    
    "@nuxtjs/axios",  
    "@nuxtjs/dotenv"  
  ],  
  /*  
   ** Axios module configuration  
   ** See https://axios.nuxtjs.org/options
   */  
  axios: {},  
  /*  
   ** Build configuration  
   */  
  build: {  
    /*  
     ** You can extend webpack config here  
     */  
    extend(config, ctx) {}  
  },  
  env: {  
    apiKey: process.env.API_KEY || ""  
  },  
  router: {  
    routes: [  
      {  
        name: "index",  
        path: "/",  
        component: "pages/index.vue"  
      },  
      {  
        name: "headlines-id",  
        path: "/headlines/:countryCode?",  
        component: "pages/headlines/_countryCode.vue"  
      }  
    ]  
  },  
  generate: {  
    routes() {  
      return getData().map(d => `headlines/${d.code}`);  
    }  
  }  
};

In the head object, we changed the title so that we display the title we want instead of the default title. In the link section, we add the Bootstrap CSS, and in the script section, we add the Bootstrap JavaScript files and jQuery, which is a dependency of Bootstrap. Since we want to build a static site, we cannot use BootstrapVue because it is dynamic. We do not want any dynamic JavaScript in the generated output, so we have to use plain Bootstrap. In the modules section, we added ”@nuxtjs/dotenv” to read the environment variables from the .env file that we created into our Nuxt app. We also added require(“dotenv”).config(); so that we get the process.env.API_KEY which can be added to this configuration file. We have to do this so that we don’t have to check in our .env file. In the env section, we have the apiKey: process.env.API_KEY || “”, which is what we get by reading the API KEY in the .env file with dotenv.

In the router section, we define the dynamic routes so that they can be viewed when users click on links with the given URLs or click on a link with such URLs. Nuxt also uses these routes to generate static files. In the generate section, we define the routes that Nuxt will traverse to generate the static files for the static website. In this case, the array of routes consist of routes for the headlines page that we created earlier. It will loop through them to get the data for them, then render them and generate the file from the rendered results. The folder structure will correspond to the routes. So since our path is /headlines/:countryCode , the generated artifact will have the headlines folder along withe all the country code as names of subfolders, and inside each folder there will be a index.html with the rendered content.

Now we are ready to deploy our website to Netlify. Create a Netlify account by going to https://www.netlify.com/. The free plan will work for our needs. Then commit your code to a Git repository hosted on GitHub, Gitlab or Bitbucket. Then when you log in to Netlify, click on New site from Git. From there, you can add your Git repository that’s hosted in one of those services. Then when you’re asked to enter the Build Command, enter node ./create-env.js && npm run generate, and the Publish directory would be dist .

After that, enter the API Key in the .env file into the Environment variables section of the website settings, which you can go to by clicking on the Environment link on the Build & deploy menu. Enter API_KEY as the key and your News API API key as the value. Then click the save button.

Once you commit and push everything in a Git repository hosted by GitHub, Gitlab or Bitbucket, Netlify will build and deploy automatically.

Categories
JavaScript JavaScript Basics

How to Check if a Variable is a Number

We can check if a variable is a number in multiple ways.

isNaN

We can check by calling isNaN with the variable as the argument. It also detects if a string’s content is a number. For example:

isNaN(1) // false  
isNaN('1') // false  
isNaN('abc') // true

Note: isNaN(null) is true .

typeof Operator

We can use the typeof operator before a variable to check if it’s a number, like so:

typeof 1 == 'number' // true  
typeof '1' == 'number' // false
Categories
JavaScript React

How To Use Hooks in React

The latest version of React (v16.8 or later) introduced Hooks, which allow us to set state more simply than before. It also makes function components smart, having the equivalent functionality and lifecycle as class-based components.

State management in a single-page app is also important. Without centralized state management, a lot of data has to be passed directly between components, which becomes unmaintainable and confusing very quickly.

Flux architecture remedies this by making apps store the state in a centralized location and the states are stored as immutable objects to prevent accidental modification.

Redux is one of the most popular libraries for state management and, with React Redux, it can connect your Redux store directly to your components, as you will see below.

In this piece, I will create an app with React and React Redux in the simplest way possible.

Create a New React App

Use the create-react-app code generator, created by the developers of React.

Here is the README and full documentation for create-react-app.

The app that will be created is an app that displays data from the Dog API.

To create the app, run npx create-react-app, and follow the instructions. This will create a new app.

Then, you are ready to install React Router. To install it, run npm i react-router-dom.

After that, install @material-ui/core and axios by running npm i @material-ui/core axios.

Material-UI provides the Material Design look to our app, and axios is an HTTP client which works in client-side apps.

In index.js, we have:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { breedsReducer, imagesReducer } from './reducers';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import { combineReducers } from 'redux'

const dogApp = combineReducers({
  breeds: breedsReducer,
  images: imagesReducer
})

const store = createStore(dogApp)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
, document.getElementById('root')
);

serviceWorker.unregister();

The file above is where the reducers are mapped to states.

As the combineReducers function is called, the store is created, which is then passed into the app, where the mapStateToProps will make the state available to the component as props.

The mapDispatchToProps allows you to set state in your component via a function in the props, as you will see below.

We add reducers to store state in a centrally available location. The states of our app are set here.

We create a file called reducer.js:

import { SET_BREEDS, SET_IMAGES } from './actions';

function breedsReducer(state = {}, action) {
   switch (action.type) {
    case SET_BREEDS:
      state = JSON.parse(JSON.stringify(action.payload));
      return state;
    default:
      return state
   }
}

function imagesReducer(state = [], action) {
   switch (action.type) {
    case SET_IMAGES:
      state = JSON.parse(JSON.stringify(action.payload));
      return state;
    default:
      return state
  }
}
export { breedsReducer, imagesReducer };

In actions.js, we add these constants for our Redux actions:

const SET_BREEDS = 'SET_BREEDS';
const SET_IMAGES = 'SET_IMAGES';export { SET_BREEDS, SET_IMAGES };

In actionCreators.js, we add:

import { SET_BREEDS, SET_IMAGES } from './actions';

const setBreeds = (breeds) => {
  return {
    type: SET_BREEDS,
    payload: breeds
  }
};

const setImages = (images) => {
  return {
    type: SET_IMAGES,
    payload: images
  }
};
export { setBreeds, setImages };

In app.js, we change the default code to:

import React, { useState, useEffect } from "react";
import './App.css';
import { setBreeds } from './actionCreators';
import { connect } from 'react-redux';
import { Router, Route, Link } from "react-router-dom";
import BreedsPage from './BreedsPage';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import Drawer from '@material-ui/core/Drawer';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import { makeStyles } from '@material-ui/core/styles';
import { createBrowserHistory as createHistory } from 'history'
const history = createHistory();
const axios = require('axios');
const useStyles = makeStyles(theme => ({
  root: {
    flexGrow: 1,
  },
  menuButton: {
    marginRight: theme.spacing(2),
  },
  title: {
    flexGrow: 1,
  },
}));

function App({ setBreeds }) {
  const classes = useStyles();
  const [initialized, setInitialized] = useState(false);
  const [state, setState] = useState({
    openDrawer: false
  });   
  const titles = {
     '/': 'Dog App',
     '/breeds': 'Get Images By Breed - Dog App',
     '/subbreeds': 'Get Images By Breed or Sub-Breed - Dog App'
   }

   history.listen((location, action) => {
     document.title = titles[location.pathname];
     setState({ openDrawer: false });
   });

  const toggleDrawer = (open) => event => {
   if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
      return;
    }
    setState({ openDrawer: open });
   };

  const links = {
    'Home': '/',
    'Breeds': '/breeds',
    'Sub-Breeds': '/subbreeds',
   }

  const getBreeds = () => {
    setInitialized(true);
    axios.get('https://dog.ceo/api/breeds/list/all')
    .then((response) => {
      setBreeds(response.data.message);
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
    });
   }
  useEffect(() => {
     if (!initialized) {
       getBreeds();
     }
  });

  return (
      <div className="App">
        <Router history={history}>
          <Drawer anchor="left" open={state.openDrawer} onClose=     {toggleDrawer(false)}>
            <List>
               <ListItem button>
                 <h2><b>Dog App</b></h2>
               </ListItem>
               {Object.keys(links).map((text) => (
                  <ListItem button key={text}>
                    <Link to={links[text]}>
                      <ListItemText primary={text} />
                   </Link>
                </ListItem>
                ))}
            </List>
          </Drawer>
           <AppBar position="static">
             <Toolbar>
               <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="Menu" onClick={toggleDrawer(true)}>
                 <i className="material-icons">menu</i>
               </IconButton>
               <Typography variant="h6" className={classes.title}>Dog App</Typography>
             </Toolbar>
          </AppBar>
          <Route path="/breeds/" component={BreedsPage} />
        </Router>
       </div>
      );
}

const mapStateToProps = (state) => ({
  breeds: state.breeds
})

const mapDispatchToProps = (dispatch) => ({
  setBreeds: breeds => dispatch(setBreeds(breeds))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

The code above has Hooks.

const [initialized, setInitialized] = useState(false);
const [state, setState] = useState({
  openDrawer: false
});

These functions are equivalent to setState functions in class-based components.

The first element in the array (initialized) is equivalent to this.state.initialized and setInitialized is equivalent to a function that calls this.setState({initialized: initializedValue}); in a class-based component.

Hooks only work with function-based components. The benefit is writing fewer lines of code to achieve the same effect of setting state.

Also, note that we have this in the above component:

useEffect(() => {
  if (!initialized) {
    getBreeds();
  }
});

As we don’t have componentDidMount, like we do in class-based components, we have to check if the component is loaded with our own flag.

The getBreeds function sets the initialized flag to true once it has run successfully so that the getBreeds function will not repeatedly run forever.

useEffect is a function that is run during every render, so be careful not to put necessary code in there.

Note the connect function at the end of the file above. This where the state connects to the component.

setBreeds is a function which returns a plain object with the action type and the payload. This allows the reducer to set the state according to the type field, which in this case would be SET_BREEDS or SET_IMAGES.

The state will be set, returned, and the new state will be available via props.breeds for breeds.

The <Route path=”/breeds/” component={BreedsPage} /> is where the route is defined. It must be inside <Router history={history}></Router>. This is the routing part of our application.

With this, we can go to the page with http://localhost:3000/breeds.

This block sets the title and hides the app drawer on the left when the route changes:

history.listen((location, action) => {
   document.title = titles[location.pathname];
   setState({ openDrawer: false });
});

We now create the pages for our app, which will be used by React Router for routing.

First, we create a page for displaying breeds, we will call it BreedPage.js.

The code will look like this:

import React from 'react';
import './BreedsPage.css';
import { setImages } from './actionCreators';
import { connect } from 'react-redux';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
import ImagesBox from './ImagesBox';
const axios = require('axios');
const useStyles = makeStyles(theme => ({
    formControl: {
        margin: theme.spacing(1),
        width: '90vw',
    },
}));

function BreedsPage({ breeds, setImages }) {
    const classes = useStyles();
    const [state, setState] = React.useState({
        breed: '',
    });

    const [initialized, setInitialized] = React.useState(false);
    const handleChange = name => event => {
        setState({
            ...state,
            [name]: event.target.value,
        });
        if (name == 'breed') {
            getImagesByBreed(event.target.value);
        }
    };

    const getImagesByBreed = (breed) => {
        axios.get(`https://dog.ceo/api/breed/${breed}/images`)
            .then((response) => {
                setImages(response.data.message);
            })
            .catch((error) => {
                console.log(error);
            })
            .finally(() => {
            });
    }

    React.useEffect(() => {
        if (!initialized) {
            setInitialized(true);
            setImages([]);
        }
    });
    return (
        <div className="App">
            <h1>Get Images By Breed</h1>
            <form>
                <FormControl className={classes.formControl}>
                    <InputLabel>Breed</InputLabel>
                    <Select
                        value={state.breed}
                        onChange={handleChange('breed')}
                    >
                        {Object.keys(breeds || {}).map(b =>
                            <MenuItem value={b} key={b}>
                                {b}
                            </MenuItem>
                        )}
                    </Select>
                </FormControl>
                <ImagesBox></ImagesBox>
            </form>
        </div>
    );
}

const mapStateToProps = state => {
    return {
        breeds: state.breeds,
        images: state.images
    }
}

const mapDispatchToProps = dispatch => ({
    setImages: images => dispatch(setImages(images))
})

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(BreedsPage);