Build an amazing Job Search App using React
Tags: javascript, react, beginners, node
Published: 2020-07-03 12:41:16

In this article, you will build a beautiful Job search app using [Github Jobs API](https://jobs.github.com/api)

By building this App, you will learn:

1. How to lazy load images in React
2. How to use React Context API for sharing data between components
3. Why React will not render the HTML used in JSX Expression and how to get it displayed correctly when required
4. How to display an alternate loading image while the actual image is downloading
5. How to create your own version of a loader using React Portal
6. How to add Load More functionality


And much more.

**You can see the live demo of the application [HERE](https://github-jobs-react-app.netlify.app/)**

**Let’s get started**

## Initial Setup

Create a new project using `create-react-app`

```javascript
create-react-app github-jobs-react-app
```

Once the project is created, delete all files from the `src` folder and create `index.js` file inside the `src` folder. Also create `actions`,`components`, `context,css`, `custom-hooks`, `images`,`reducers`, `router`, `store` and `utils` folders inside the `src` folder.

Install the necessary dependencies

```javascript
yarn add axios@0.19.2 bootstrap@4.5.0 lodash@4.17.15 moment@2.27.0 node-sass@4.14.1 prop-types@15.7.2 react-bootstrap@1.0.1 react-redux@7.2.0 redux@4.0.5 redux-thunk@2.3.0
```

Create a new folder with the name `server` outside the `src` folder and execute the following command from `server` folder

```javascript
yarn init -y
```

This will create a `package.json` file inside the `server` folder.

Install the required dependencies from `server` folder

```javascript
yarn add axios@0.19.2 express@4.17.1 cors@2.8.5 nodemon@2.0.4
```

Create a new file with name `.gitignore` inside `server` folder and add the following line inside it so `node_modules` folder will not be version controlled

```javascript
node_modules
```

## Initial Page Display Changes

Now, Create a new file `styles.scss` inside `src/css` folder and add content from [HERE](https://gist.github.com/myogeshchavan97/f288ebcb5f2cc53398e44eb780f5628b) inside it.

Create a new file `jobs.js` inside `src/reducers` folder with the following content

```javascript
const jobsReducer = (state = [], action) => {
  switch (action.type) {
    case 'SET_JOBS':
      return action.jobs;
    case 'LOAD_MORE_JOBS':
      return [...state, ...action.jobs];
    default:
      return state;
  }
};
export default jobsReducer;
```

In this file, we are adding the new jobs data coming from API in redux using `SET_JOBS` action and using `LOAD_MORE_JOBS` action we are getting more jobs and adding it to already existing jobs array using the spread operator.

```javascript
[...state, ...action.jobs]
```

Create a new file `errors.js` inside `src/reducers` folder with the following content

```javascript
const errorsReducer = (state = {}, action) => {
  switch (action.type) {
    case 'SET_ERRORS':
      return {
        error: action.error
      };
    case 'RESET_ERRORS':
      return {};
    default:
      return state;
  }
};
export default errorsReducer;
```

In this file, we are adding the API error if any into the redux store by dispatching `SET_ERRORS` action and removing the error object from redux store if there is no error while getting a response from API by dispatching `RESET_ERRORS` action.

Create a new file `store.js` inside `src` folder with the following content

```javascript
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import jobsReducer from '../reducers/jobs';
import errorsReducer from '../reducers/errors';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  combineReducers({
    jobs: jobsReducer,
    errors: errorsReducer
  }),
  composeEnhancers(applyMiddleware(thunk))
);

console.log(store.getState());

export default store;
```

In this file, we are creating a redux store that uses `combineReducers` and added thunk from `redux-thunk` as a middleware for managing the Asynchronous API handling.

We also added the redux devtool configuration using `composeEnhandlers`.
If you are new to redux-thunk and redux devtool configuration, check out my previous article [HERE](https://medium.com/javascript-in-plain-english/how-to-integrate-redux-thunk-into-your-react-application-cd92b87c1629) to understand how to use it.

Now, inside `src/index.js` file add the following content

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store/store';
import HomePage from './components/HomePage';
import 'bootstrap/dist/css/bootstrap.min.css';
import './css/styles.scss';

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

In this file, we are using `Provider` component from `react-redux` which will allow us to share the store data to `HomePage` and all its child components.

Now, Create a new file `HomePage.js` inside `src/components` folder with the following content.

```javascript
import React from 'react';

const HomePage = () => {
  return <div className="container">Home Page</div>;
};

export default HomePage;
```

Now, open `public/index.html` and change

```javascript
<title>React App</title>
```

To

```js
<title>Github Job Search</title>
```

Now start the React application by running following command from `github-jobs-react-app` folder

```javascript
yarn start
```

You will see the application with Home Page text displayed

![initial screen](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/36055e591d40831dc75885f351e07e85dec0291a/initial.png)

## Adding Basic Search UI

Now, create a new file `Header.js` inside `components` folder with the following content

```javascript
import React from 'react';

const Header = () => (
  <header className="header">
    <div className="title">Github Job Search</div>
  </header>
);

export default Header;
```

Create a new file `Search.js` inside `components` folder with the following content

```javascript
import React, { useState } from 'react';
import { Form, Button, Row, Col } from 'react-bootstrap';

const Search = (props) => {
  const [state, setState] = useState({
    description: '',
    location: '',
    full_time: false
  });

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    if (name === 'full_time') {
      setState((prevState) => ({ ...state, [name]: !prevState.full_time }));
    } else {
      setState({ ...state, [name]: value });
    }
  };

  const handleSearch = (event) => {
    event.preventDefault();
    console.log(state);
  };

  return (
    <div className="search-section">
      <Form className="search-form" onSubmit={handleSearch}>
        <Row>
          <Col>
            <Form.Group controlId="description">
              <Form.Control
                type="text"
                name="description"
                value={state.description || ''}
                placeholder="Enter search term"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
          <Col>
            <Form.Group controlId="location">
              <Form.Control
                type="text"
                name="location"
                value={state.location || ''}
                placeholder="Enter location"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
          <Col>
            <Button variant="primary" type="submit" className="btn-search">
              Search
            </Button>
          </Col>
        </Row>
        <div className="filters">
          <Form.Group controlId="full_time">
            <Form.Check
              type="checkbox"
              name="full_time"
              className="full-time-checkbox"
              label="Full time only"
              checked={state.full_time}
              onChange={handleInputChange}
            />
          </Form.Group>
        </div>
      </Form>
    </div>
  );
};
export default Search;
```

In this file, we have added two input text fields to get the description and location from the user and added a checkbox to get only full-time jobs.

We also added an `onChange` handler to each input field to update the state value.

Now, open `HomePage.js` and replace it with the following content

```javascript
import React from 'react';
import Header from './Header';
import Search from './Search';

const HomePage = () => {
  return (
    <div>
      <Header />
      <Search />
    </div>
  );
};

export default HomePage;
```

Now, If you enter the values in input fields and click on `Search` button, you will see the entered data displayed in the console

![search page](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/939e3730414a680f712b908179ff8085ef1a660b/search.png)

## Displaying List of Jobs on UI

Now, create `errors.js` inside `src/actions` folder with the following content

```javascript
export const setErrors = (error) => ({
  type: 'SET_ERRORS',
  error
});

export const resetErrors = () => ({
  type: 'RESET_ERRORS'
});
```

In this file, we have added action creator functions which we will call to dispatch actions to the reducer.

Create a new file `constants.js` inside `utils` folder with the following content

```javascript
export const BASE_API_URL = 'http://localhost:5000';
```
Create a new file `jobs.js` inside `src/actions` folder with the following content

```javascript
import axios from 'axios';
import moment from 'moment';
import { BASE_API_URL } from '../utils/constants';
import { setErrors } from './errors';

export const initiateGetJobs = (data) => {
  return async (dispatch) => {
    try {
      let { description, full_time, location, page } = data;
      description = description ? encodeURIComponent(description) : '';
      location = location ? encodeURIComponent(location) : '';
      full_time = full_time ? '&full_time=true' : '';

      if (page) {
        page = parseInt(page);
        page = isNaN(page) ? '' : `&page=${page}`;
      }

      const jobs = await axios.get(
        `${BASE_API_URL}/jobs?description=${description}&location=${location}${full_time}${page}`
      );
      const sortedJobs = jobs.data.sort(
        (a, b) =>
          moment(new Date(b.created_at)) - moment(new Date(a.created_at))
      );
      return dispatch(setJobs(sortedJobs));
    } catch (error) {
      error.response && dispatch(setErrors(error.response.data));
    }
  };
};

export const setJobs = (jobs) => ({
  type: 'SET_JOBS',
  jobs
});

export const setLoadMoreJobs = (jobs) => ({
  type: 'LOAD_MORE_JOBS',
  jobs
});
```

In this file, we have added an `initiateGetJobs` function which will get the JSON data by making an API call to the Express server in Node.js and once the data is received, `SET_JOBS` action is dispatched which will add all the jobs data into the redux store by executing the `SET_JOBS` switch case from `reducers/jobs.js` file.

Now, create a new file` server.js` inside `server` folder with the following content

```javascript
const path = require('path');
const axios = require('axios');
const cors = require('cors');
const express = require('express');
const app = express();
const PORT = process.env.PORT || 5000;
const buildPath = path.join(__dirname, '..', 'build');

app.use(express.static(buildPath));
app.use(cors());

app.get('/jobs', async (req, res) => {
  try {
    let { description = '', full_time, location = '', page = 1 } = req.query;
    description = description ? encodeURIComponent(description) : '';
    location = location ? encodeURIComponent(location) : '';
    full_time = full_time === 'true' ? '&full_time=true' : '';

    if (page) {
      page = parseInt(page);
      page = isNaN(page) ? '' : `&page=${page}`;
    }

    const query = `https://jobs.github.com/positions.json?description=${description}&location=${location}${full_time}${page}`;
    const result = await axios.get(query);
    res.send(result.data);
  } catch (error) {
    res.status(400).send('Error while getting list of jobs.Try again later.');
  }
});

app.listen(PORT, () => {
  console.log(`server started on port ${PORT}`);
});
```

In this file, we have created a `/jobs` get API using `Express` server.

Here, we are calling the `Github Jobs API` to get the list of available jobs by passing the `description` and `location`. 

By default, the API gives a list of the latest `50` jobs only but we can get more jobs by sending `page` query parameter with values 1, 2, 3, etc.

So we are validating the page query parameter by the following code

```javascript
if (page) {
  page = parseInt(page);
  page = isNaN(page) ? '' : `&page=${page}`;
}
```

If we want to only the full-time jobs then we need to add an additional `full_time` parameter to query string with the value of `true`

```javascript
full_time = full_time === 'true' ? '&full_time=true' : '';
```

Then finally we are creating the API URL by combining all parameter values.

```javascript
`https://jobs.github.com/positions.json?description=${description}&location=${location}${full_time}${page}`;
```


The reason for adding `encodeURIComponent` for each input field is to convert special characters if any like space to %20.

If you noticed, we have added the same parsing code in `initiateGetJobs` function also which is inside `actions/jobs.js` file. 


The reason for including it in server code also is that we can also directly access the `/jobs` get API without any application for just for the additional check we added the conditions.

Now, create a new file `JobItem.js` inside the `components` folder with the following content


```javascript
import React from 'react';
import moment from 'moment';

const JobItem = (props) => {
  const {
    id,
    type,
    created_at,
    company,
    location,
    title,
    company_logo,
    index
  } = props;

  return (
    <div className="job-item" index={index + 1}>
      <div className="company-logo">
        <img src={company_logo} alt={company} width="100" height="100" />
      </div>
      <div className="job-info">
        <div className="job-title">{title}</div>
        <div className="job-location">
          {location} | {type}
        </div>
        <div className="company-name">{company}</div>
      </div>
      <div className="post-info">
        <div className="post-time">
          Posted {moment(new Date(created_at)).fromNow()}
        </div>
      </div>
    </div>
  );
};

export default JobItem;
```

In this file, we are displaying the data coming from API
Create a new file `Results.js` inside `components` folder with the following content

```js
import React from 'react';
import JobItem from './JobItem';

const Results = ({ results }) => {
  return (
    <div className="search-results">
      {results.map((job, index) => (
        <JobItem key={job.id} {...job} index={index} />
      ))}
    </div>
  );
};

export default Results;
```

In this file, we are looping through each job object from results array and we are passing the individual job data to display in `JobItem` component created previously.

Now, open `components/HomePage.js` file and replace it with the following content

```js
import React, { useState, useEffect } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { initiateGetJobs } from '../actions/jobs';
import { resetErrors } from '../actions/errors';
import Header from './Header';
import Search from './Search';
import Results from './Results';

const HomePage = (props) => {
  const [results, setResults] = useState([]);
  const [errors, setErrors] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    setResults(props.jobs);
  }, [props.jobs]);

  useEffect(() => {
    setErrors(props.errors);
  }, [props.errors]);

  const loadJobs = (selection) => {
    const { dispatch } = props;
    const { description, location, full_time, page = 1 } = selection;
    dispatch(resetErrors());
    setIsLoading(true);
    dispatch(initiateGetJobs({ description, location, full_time, page }))
      .then(() => {
        setIsLoading(false);
      })
      .catch(() => setIsLoading(false));
  };

  const handleSearch = (selection) => {
    loadJobs(selection);
  };

  return (
    <div>
      <Header />
      <Search onSearch={handleSearch} />
      {!_.isEmpty(errors) && (
        <div className="errorMsg">
          <p>{errors.error}</p>
        </div>
      )}
      <Results results={results} />
      {isLoading && <p className="loading">Loading...</p>}
    </div>
  );
};

const mapStateToProps = (state) => ({
  jobs: state.jobs,
  errors: state.errors
});

export default connect(mapStateToProps)(HomePage);
```

In this file, we are starting to use React Hooks now. If you are new to React Hooks check out my previous article for an introduction to Hooks [HERE](https://levelup.gitconnected.com/an-introduction-to-react-hooks-50281fd961fe)


Let’s understand the code from the `HomePage` component.
Initially, we declared state variables using `useState` hook to store the result from API in an array and a flag for showing the loading and object for an error indication.

```js
const [results, setResults] = useState([]);
const [errors, setErrors] = useState(null);
const [isLoading, setIsLoading] = useState(false);
```

Then we call the `useEffect` Hook to get the list of jobs and error if any

```js
useEffect(() => {
  setResults(props.jobs);
}, [props.jobs]);
useEffect(() => {
  setErrors(props.errors);
}, [props.errors]);
```

We implement the `componentDidUpdate` lifecycle method of class components using the `useEffect` hook by passing the dependency array as the second argument. So each of these `useEffect` hooks will be executed only when their dependency changes For example when `props.jobs` changes or `props.errors` changes. The data is available in props because we have added a `mapStateToProps` method at the end of the file

```js
const mapStateToProps = (state) => ({
  jobs: state.jobs,
  errors: state.errors
});
```

and passed it to connect the method of `react-redux` library.


```js
export default connect(mapStateToProps)(HomePage);
```

Then, we are passing the `onSearch` prop to the `Search` component whose value is the `handleSearch` function.

```js
<Search onSearch={handleSearch} />
```

From inside this function, we are calling the `loadJobs` function which is calling the `initiateGetJobs` action creator function to make an API call to the `Express` server.

We are passing the `onSearch` prop to the `Search` component, but we are not using it yet, so let’s use it first.

Open Search.js component and change

```js
const handleSearch = (event) => {
  event.preventDefault();
  console.log(state);
};
```

to

```js
const handleSearch = (event) => {
  event.preventDefault();
  console.log(state);
  props.onSearch(state);
};
```

So now, when we click the `Search` button, we are calling `onSearch` function passed as a prop to the `Search` component from the `HomePage` component.

Now, let’s run the application. Before running it, we need to make some changes.

Open `server/package.json` file and add start script inside it

```js
"start": "nodemon server.js"
```

So the `package.json` from `server` folder will look like this


```js
{
  "name": "server",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon server.js"
  },
  "dependencies": {
    "axios": "0.19.2",
    "cors": "2.8.5",
    "express": "4.17.1",
    "nodemon": "^2.0.4",
  }
}
```

Now, execute start command from `server` folder

```js
yarn run start
```

This will start the `Express` server.

Open another terminal and from `github-jobs-react-app` folder, execute `yarn run start` command. This will start your React application.

The `description` and `location` are optional parameters to the `Github Jobs API` so If you don’t enter any value and click on the `Search` button, you will get all the available jobs displayed on the screen sorted by the posted date

![results page](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/d29e7d7a197a61e4d6e459a22618a43f07cc7ab1/result.png)

![working search](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/d29e7d7a197a61e4d6e459a22618a43f07cc7ab1/working_search.gif)

The data is sorted by creation date in `initiateGetJobs` function inside the `actions/jobs.js` file

```js
const sortedJobs = jobs.data.sort(
  (a, b) =>
    moment(new Date(b.created_at)) - moment(new Date(a.created_at))
);
```

If you want to dive into details of how this code sorted the data, check out my previous article [HERE](https://levelup.gitconnected.com/array-sort-method-and-its-gotchas-5859ece92e8d)


You can find source code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/code_till_display_jobs)

## Displaying Job Details Page

Now, let’s get the details of the Job when we click on any of the Job from the 

Create a new file `JobDetails.js` inside `components` folder with the following content

```js
import React from 'react';

const JobDetails = ({ details, onResetPage }) => {
  const {
    type,
    title,
    description,
    location,
    company,
    company_url,
    company_logo,
    how_to_apply
  } = details;

  return (
    <div className="job-details">
      <div className="back-link">
        <a href="/#" onClick={onResetPage}>
          &lt;&lt; Back to results
        </a>
      </div>
      <div>
        {type} / {location}
      </div>
      <div className="main-section">
        <div className="left-section">
          <div className="title">{title}</div>
          <hr />
          <div className="job-description">{description}</div>
        </div>
        <div className="right-section">
          <div className="company-details">
            <h3>About company</h3>
            <img src={company_logo} alt={company} className="company-logo" />
            <div className="company-name">{company}</div>
            <a className="company-url" href={company_url}>
              {company_url}
            </a>
          </div>
          <div className="how-to-apply">
            <h3>How to apply</h3>
            <div>{how_to_apply}</div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default JobDetails;
```

Here, we are displaying the description of the job details.

Now, we need a flag that will decide when to display the details page and when to display the list of jobs. 

So create a new state variable inside `HomePage.js` file with a default value of `home` and a variable to track id of the job clicked

```js
const [jobId, setJobId] = useState(-1);
const [page, setPage] = useState('home');
```

Open `HomePage.js` file and replace it with the following content

```js
import React, { useState, useEffect } from 'react';
import _ from 'lodash';
import { connect } from 'react-redux';
import { initiateGetJobs } from '../actions/jobs';
import { resetErrors } from '../actions/errors';
import Header from './Header';
import Search from './Search';
import Results from './Results';
import JobDetails from './JobDetails';

const HomePage = (props) => {
  const [results, setResults] = useState([]);
  const [errors, setErrors] = useState(null);
  const [isLoading, setIsLoading] = useState(false);
  const [jobId, setJobId] = useState(-1);
  const [page, setPage] = useState('home');

  useEffect(() => {
    setResults(props.jobs);
  }, [props.jobs]);

  useEffect(() => {
    setErrors(props.errors);
  }, [props.errors]);

  const loadJobs = (selection) => {
    const { dispatch } = props;
    const { description, location, full_time, page = 1 } = selection;
    dispatch(resetErrors());
    setIsLoading(true);
    dispatch(initiateGetJobs({ description, location, full_time, page }))
      .then(() => {
        setIsLoading(false);
      })
      .catch(() => setIsLoading(false));
  };

  const handleSearch = (selection) => {
    loadJobs(selection);
  };

  const handleItemClick = (jobId) => {
    setPage('details');
    setJobId(jobId);
  };

  const handleResetPage = () => {
    setPage('home');
  };

  let jobDetails = {};
  if (page === 'details') {
    jobDetails = results.find((job) => job.id === jobId);
  }
  return (
    <div>
      <div className={`${page === 'details' && 'hide'}`}>
        <Header /> <Search onSearch={handleSearch} />
        {!_.isEmpty(errors) && (
          <div className="errorMsg">
            <p>{errors.error}</p>
          </div>
        )}
        {isLoading && <p className="loading">Loading...</p>}
        <div>
          <Results results={results} onItemClick={handleItemClick} />
        </div>
      </div>
      <div className={`${page === 'home' && 'hide'}`}>
        <JobDetails details={jobDetails} onResetPage={handleResetPage} />
      </div>
    </div>
  );
};

const mapStateToProps = (state) => ({
  jobs: state.jobs,
  errors: state.errors
});

export default connect(mapStateToProps)(HomePage);
```

In this file, we have added `handleItemClick` and `handleResetPage` functions.
Also when we click on the details page, we filter out the job from the `results` array

```js
let jobDetails = {};
if (page === 'details') {
  jobDetails = results.find((job) => job.id === jobId);
}
```

and pass it to `JobDetails` component

```js
<JobDetails details={jobDetails} onResetPage={handleResetPage} />
```

If the page value is `home`, we are displaying the `Header`, `Search` and the `Results` components and if the value is `details`, we are displaying the `JobDetails` page as we are adding the hide CSS class to display respective components

Note, we also passed `onItemClick` prop to `Results` component.

```js
<Results results={results} onItemClick={handleItemClick} />
```

and from `Results` component, we are passing it down to `JobItem` component and inside that component we have added that handler to the topmost div

```js
<div className="job-item" index={index + 1} onClick={() => onItemClick(id)}>
```

where we are destructuring the id from props and passing it to `onItemClick` function

Now, restart your `React` application and `Express` server by running `yarn run start` command and verify the changes

![details page](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/ce11a591e62a8bb55c594e42aa9288cea496ba46/details.gif)

So now, when we click on any job, we can see the details of the job but if you noticed the details page, you can see that the HTML of the details page is displayed as it is which means the <p> tag is displayed as static text instead of rendering the paragraph.

_This because by default React does not directly display the HTML content when used inside the JSX Expression to avoid the Cross Site Scripting (XSS) attacks. React escapes all the html content provided in the JSX Expression which is written in curly brackets so it will be printed as it is._

![api](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/120f96d792a6c004eee751bc02a8e4835d66977d/api.png)

If you check the above API response, you can see that the description field contains the HTML content and we are printing the description in `JobDetails.js` file as

```js
<div className="job-description">{description}</div>
```

Also, in the `how to apply` section

```js
<div>{how_to_apply}</div>
```

To display the HTML content if its the requirement as in our case, we need to use a special prop called `dangerouslySetInnerHTML` and pass it the HTML in the `__html` field as shown below

```js
<div className="job-description" dangerouslySetInnerHTML={{ __html: description }}></div>
```

and

```js
<div dangerouslySetInnerHTML={{ __html: how_to_apply }}></div>
```

So make these changes in `JobDetails.js` file and check the application now, You will see the HTML rendered correctly

![correct html](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/009618d330c068b28272d8283304f3f287937aae/correct_html.png)

Awesome!

Just one more thing, while building application, it's not good to keep sending requests to the actual server every time we are testing so create a new file jobs.json inside public folder by saving the response of API from [HERE](https://gist.github.com/myogeshchavan97/08fdb2c7a070283f752a174ff52f6cf8) and in `actions/jobs.js` file add a comment for the following line

```js
const jobs = await axios.get(
  `${BASE_API_URL}/jobs?description=${description}&location=${location}${full_time}${page}`
);
```

and add the following code below it.

```js
const jobs = await axios.get('./jobs.json');
```

![job action](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/324d039d36ba2c007a416970148a3f92640fc5fb/job_action.png)

So now, whenever we click on the Search button, we will take data from the JSON file stored in public folder which will give a faster response and will also not increase the number of requests to the actual Github API.

If you are using some other APIs, they might be limited to a specific number of requests and might charge you if you exceed the limit.
Note: Github Jobs API is free and will not charge you for the number of requests but still it’s good to use a cached response and only when you need to handle proper scenarios, use the actual API instead of cached one.

You can find code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/job_details_page_display)

## Using Context API to Avoid Prop Drilling

Now, if you check the HomePage component, we are passing the `onItemClick` function to `Results` component and `Results` component passes it down to `JobItem` component without using it So to avoid this prop drilling and to make the JSX returned from `HomePage` component a lot simpler we can use `React Context API` here.

If you are not familiar with `React Context API`, check out my previous article [HERE](https://medium.com/swlh/what-is-context-api-in-react-and-how-to-use-it-in-react-app-dedbcdd78801)

Inside `src/context` folder, create a new file `jobs.js` with the following content

```js
import React from 'react';

const JobsContext = React.createContext();

export default JobsContext;
```

Here, we are just creating a `Context` which we can use to access data in other components
In the `HomePage.js` file, import this context at the top of the file

```js
import JobsContext from '../context/jobs';
```

and just before returning the JSX, create a value object with the data we want to access in other components

```js
const value = {
  results,
  details: jobDetails,
  onSearch: handleSearch,
  onItemClick: handleItemClick,
  onResetPage: handleResetPage
};
```

Change the returned JSX from

```js
return (
  <div>
    <div className={`${page === 'details' && 'hide'}`}>
      <Header />
      <Search onSearch={handleSearch} />
      {!_.isEmpty(errors) && (
        <div className="errorMsg">
          <p>{errors.error}</p>
        </div>
      )}
      {isLoading && <p className="loading">Loading...</p>}
      <Results results={results} onItemClick={handleItemClick} />
    </div>
    <div className={`${page === 'home' && 'hide'}`}>
      <JobDetails details={jobDetails} onResetPage={handleResetPage} />
    </div>
  </div>
);
```

to

```js
return (
  <JobsContext.Provider value={value}>
    <div className={`${page === 'details' && 'hide'}`}>
      <Header /> 
      <Search />
      {!_.isEmpty(errors) && (
        <div className="errorMsg">
          <p>{errors.error}</p>
        </div>
      )}
      {isLoading && <p className="loading">Loading...</p>} 
      <Results />
    </div>
    <div className={`${page === 'home' && 'hide'}`}>
      <JobDetails />
    </div>
  </JobsContext.Provider>
);
```

As you can see, we have removed all the props passed to `Search`, `Results` and `JobDetails` component and we are using

```js
<JobsContext.Provider value={value}>
```

to pass all of those values because `Provider` component requires a value prop and now all the components in between the opening and closing `JobsContext.Provider` tag can access any value from the value object passed as prop.

Now, open `Search.js` file and add the import for the context at the top. Also import the `useContext` hook at the top

```js
import React, { useState, useContext } from 'react';
```

Now, to access the data from value object add the following code inside `Search` component


```js
const { onSearch } = useContext(JobsContext);
```

Now, you can remove the props parameter passed to the component and inside `handleSearch` function, change

```js
props.onSearch(state);
```

to just

```js
onSearch(state);
```

Now, your `Search` component will look like this

```js
import React, { useState, useContext } from 'react';
import { Form, Button, Row, Col } from 'react-bootstrap';
import JobsContext from '../context/jobs';

const Search = () => {
  const { onSearch } = useContext(JobsContext);
  const [state, setState] = useState({
    description: '',
    location: '',
    full_time: false
  });

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    if (name === 'full_time') {
      setState((prevState) => ({ ...state, [name]: !prevState.full_time }));
    } else {
      setState({ ...state, [name]: value });
    }
  };

  const handleSearch = (event) => {
    event.preventDefault();
    console.log(state);
    onSearch(state);
  };

  return (
    <div className="search-section">
      <Form className="search-form" onSubmit={handleSearch}>
        <Row>
          <Col>
            <Form.Group controlId="description">
              <Form.Control
                type="text"
                name="description"
                value={state.description || ''}
                placeholder="Enter search term"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
          <Col>
            <Form.Group controlId="location">
              <Form.Control
                type="text"
                name="location"
                value={state.location || ''}
                placeholder="Enter location"
                onChange={handleInputChange}
              />
            </Form.Group>
          </Col>
          <Col>
            <Button variant="primary" type="submit" className="btn-search">
              Search
            </Button>
          </Col>
        </Row>
        <div className="filters">
          <Form.Group controlId="full_time">
            <Form.Check
              type="checkbox"
              name="full_time"
              className="full-time-checkbox"
              label="Full time only"
              checked={state.full_time}
              onChange={handleInputChange}
            />
          </Form.Group>
        </div>
      </Form>
    </div>
  );
};

export default Search;
```

Now, let’s use the context in `Results` component

Remove both the props passed to the component

Import context at the top of the file

```js
import JobsContext from '../context/jobs';
```

Take the required values out of the context

```js
const { results } = useContext(JobsContext);
```

Now, you can remove the `onItemClick` prop passed to `JobItem` component

```js
import React, { useContext } from 'react';
import JobItem from './JobItem';
import JobsContext from '../context/jobs';
const Results = () => {
  const { results } = useContext(JobsContext);
  return (
    <div className="search-results">
      {results.map((job, index) => (
        <JobItem key={job.id} {...job} index={index} />
      ))}
    </div>
  );
};
export default Results;
```

Now, let’s refactor the `JobDetails` component

Import context at the top of the file

```js
import JobsContext from '../context/jobs';
```

Take the required values out of the context

```js
const { details, onResetPage } = useContext(JobsContext);
```

Now, your `JobDetails.js` file will look like this

```js
import React, { useContext } from 'react';
import JobsContext from '../context/jobs';

const JobDetails = () => {
  const { details, onResetPage } = useContext(JobsContext);
  const {
    type,
    title,
    description,
    location,
    company,
    company_url,
    company_logo,
    how_to_apply
  } = details;

  return (
    <div className="job-details">
      <div className="back-link">
        <a href="/#" onClick={onResetPage}>
          &lt;&lt; Back to results
        </a>
      </div>
      <div>
        {type} / {location}
      </div>
      <div className="main-section">
        <div className="left-section">
          <div className="title">{title}</div> <hr />
          <div
            className="job-description"
            dangerouslySetInnerHTML={{ __html: description }}
          ></div>
        </div>
        <div className="right-section">
          <div className="company-details">
            <h3>About company</h3>
            <img src={company_logo} alt={company} className="company-logo" />
            <div className="company-name">{company}</div>
            <a className="company-url" href={company_url}>
              {company_url}
            </a>
          </div>
          <div className="how-to-apply">
            <h3>How to apply</h3>
            <div dangerouslySetInnerHTML={{ __html: how_to_apply }}></div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default JobDetails;
```

Now, let’s refactor the `JobItem` component

Import context at the top of the file

```js
import JobsContext from '../context/jobs';
```

Take the required values out of the context

```js
const { onItemClick } = useContext(JobsContext);
```

Now, your `JobItem.js` file will look like this

```js
import React, { useContext } from 'react';
import moment from 'moment';
import JobsContext from '../context/jobs';

const JobItem = (props) => {
  const { onItemClick } = useContext(JobsContext);
  const {
    id,
    type,
    created_at,
    company,
    location,
    title,
    company_logo,
    index
  } = props;

  return (
    <div className="job-item" index={index + 1} onClick={() => onItemClick(id)}>
      <div className="company-logo">
        <img src={company_logo} alt={company} width="100" height="100" />
      </div>
      <div className="job-info">
        <div className="job-title">{title}</div>
        <div className="job-location">
          {location} | {type}
        </div>
        <div className="company-name">{company}</div>
      </div>
      <div className="post-info">
        <div className="post-time">
          Posted {moment(new Date(created_at)).fromNow()}
        </div>
      </div>
    </div>
  );
};

export default JobItem;
```

Now, check your application and you can see that application works the same as previously but now we have avoided the unnecessary prop drilling and made code easier to understand

You can find code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/changes_using_context)

## Reset Scroll Position 

One thing you might have noticed is that, when we scroll down a bit on the jobs list and click on any of the job, the page scroll remains at the same place and we see the bottom of the page instead of the top

![scroll issue](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/a8cd61266120ea3a97133361b999a680066e8c1b/scroll_issue.gif)

This is because we are just adding hide class to components that are not needed when we click on any job so the scroll position does not change.

To fix this, open `JobDetail.js` file and add the following code

```js
useEffect(() => {
  window.scrollTo(0, 0);
}, []);
```

So now, when the `JobDetails` component is displayed, we are automatically displayed top of the page.

The empty array specifies that this code should be executed only when the component is mounted (similar to `componentDidMount` lifecycle method) and never again.

We also need to make sure that, the `JobDetails` component is only loaded when we click on any of the job so open `HomePage.js` file and change

```js
<div className={`${page === 'home' && 'hide'}`}>
  <JobDetails />
</div>
```

to

```js
<div className={`${page === 'home' && 'hide'}`}>
  {page === 'details' && <JobDetails />}
</div>
```

Now, if you check the application, you can see that the top of the page is displayed when clicked on any job.

## Adding Load More Functionality

As we already know, we are getting only the latest `50` jobs when we hit the Github Jobs API, to get more jobs, we need to pass the `page` query parameter with an incremented number so let’s implement the load more functionality into our application.

Let's create a `pageNumber` state variable in `HomePage.js` with an initial value of `1` and `selection` state variable

```js
const [pageNumber, setPageNumber] = useState(1);
const [selection, setSelection] = useState(null);
```

Add the code to show the load more button in `HomePage.js` file

```js
{
  results.length > 0 && _.isEmpty(errors) && (
    <div className="load-more" onClick={isLoading ? null : handleLoadMore}>
      <button disabled={isLoading} className={`${isLoading ? 'disabled' : ''}`}>
        Load More Jobs
      </button>
    </div>
  );
}
```

Also, move the loading condition from before the <Results /> to after it

So your JSX returned form `HomePage.js` will look like this

```js
return (
  <JobsContext.Provider value={value}>
    <div className={`${page === 'details' && 'hide'}`}>
      <Header /> <Search />
      {!_.isEmpty(errors) && (
        <div className="errorMsg">
          <p>{errors.error}</p>
        </div>
      )}
      <Results />
      {isLoading && <p className="loading">Loading...</p>}
      {results.length > 0 && _.isEmpty(errors) && (
        <div className="load-more" onClick={isLoading ? null : handleLoadMore}>
          <button
            disabled={isLoading}
            className={`${isLoading ? 'disabled' : ''}`}
          >
            Load More Jobs
          </button>
        </div>
      )}
    </div>
    <div className={`${page === 'home' && 'hide'}`}>
      {page === 'details' && <JobDetails />}
    </div>
  </JobsContext.Provider>
);
```

In the add more button div above, we are disabling the button once the user clicks on it by adding the disabled `class` and `disabled` attribute

```js
className={`${isLoading ? 'disabled' : ''}`}
```

We are also making sure that the `handleLoadMore` function will not be executed when button is disabled so it's disabled by returning `null` from the `onClick` handler. This is useful in case the user removes the disabled attribute by editing it in dev tool.

Now add the `handleLoadMore` function inside the `HomePage` component

```js
const handleLoadMore = () => {
  loadJobs({ ...selection, page: pageNumber + 1 });
  setPageNumber(pageNumber + 1);
};
```

Now, we are passing the incremented page number to `loadJobs` function but we need to further pass it to our action dispatcher function so inside the `loadJobs` function just before `dispatch(resetErrors());` add the following code

```js
let isLoadMore = false;
if (selection.hasOwnProperty('page')) {
  isLoadMore = true;
}
```

and pass the isLoadMore as the last parameter to `initiateGetJobs` function.
So your `loadJobs` function will look like this

```js
const loadJobs = (selection) => {
  const { dispatch } = props;
  const { description, location, full_time, page = 1 } = selection;
  let isLoadMore = false;
  if (selection.hasOwnProperty('page')) {
    isLoadMore = true;
  }
  dispatch(resetErrors());
  setIsLoading(true);
  dispatch(
    initiateGetJobs({ description, location, full_time, page }, isLoadMore)
  )
    .then(() => {
      setIsLoading(false);
    })
    .catch(() => setIsLoading(false));
};
```

and inside the function `handleSearchction`, call the `setSelection` function for setting the state

```js
const handleSearch = (selection) => {
  loadJobs(selection);
  setSelection(selection);
};
```

Now, open `actions/jobs.js` file and accept the `isLoadMore` as the second parameter

```js
export const initiateGetJobs = (data, isLoadMore) => {
```

and change

```js
return dispatch(setJobs(sortedJobs));
```

to

```js
if (isLoadMore) {
  return dispatch(setLoadMoreJobs(sortedJobs));
} else {
  return dispatch(setJobs(sortedJobs));
}
```

In this code, If the load more button is clicked then we are calling `setLoadMoreJobs` function to add new jobs to already existing `results` array.

If `isLoadMore` is false means we clicked on the `Search` button on the page then we are calling `setJobs` function to add the results in a new array.

Now, restart the `React` application by running `yarn run start` command and you can see that load more functionality is working as expected.

![load more](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/f461fa519439a5bacb7515997f639e67522aafb3/load_more.gif)

You can find code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/added_load_more_functionality)

## Creating Custom Loader Component For Overlay

But one thing you will notice is that we have moved the loading message to above the load more button so if we are entering some values in description and location fields when the results are already displayed and we click on `Search` button, we will not see the loading message because, for that, we need to scroll the page. This is not good user experience.

Also even though loading message is displayed, the user can click on any of the job even when loading is going on, which is also not expected.
So let’s create our own loader using `React Portal` to display the overlay so the user will not be able to click on any of the job when loading and we will also see a clear indication of loading.

If you are not aware of `React Portal`, check out my previous article [HERE](https://levelup.gitconnected.com/how-to-create-custom-reusable-toast-component-in-react-d556bf624b85)

Create a new file `Loader.js` inside `components` folder with the following content

```js
import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

const Loader = (props) => {
  const [node] = useState(document.createElement('div'));
  const loader = document.querySelector('#loader');

  useEffect(() => {
    loader.appendChild(node).classList.add('message');
  }, [loader, node]);

  useEffect(() => {
    if (props.show) {
      loader.classList.remove('hide');
      document.body.classList.add('loader-open');
    } else {
      loader.classList.add('hide');
      document.body.classList.remove('loader-open');
    }
  }, [loader, props.show]);

  return ReactDOM.createPortal(props.children, node);
};

export default Loader;
```

Now open `public/index.html` and alongside the div with id `root` add another div with id `loader`

```js
<div id="root"></div>
<div id="loader"></div>
```

The `ReactDOM.createPortal` method which we have used in `Loader.js` will create a loader inside the div with id `loader` so it will be outside out `React` application DOM hierarchy and hence we can use it to provide an overlay for our entire application. This is the primary reason for using the `React Portal` for creating a loader.

So even if we will include the `Loader` component in `HomePage.js` file, it will be rendered outside all the divs but inside the div with id loader.

In the `Loader.js` file, we have first created a div where will add a loader message

```js
const [node] = useState(document.createElement('div'));
```

Then, we are adding the `message` class to that div and adding that div to the div added in `index.html`

```js
document.querySelector('#loader').appendChild(node).classList.add('message');
```

and based on the show prop passed from the `HomePage` component, we will add or remove the `hide` class and then finally we will render the `Loader` component using

```js
ReactDOM.createPortal(props.children, node);
```

Then we add or remove the `loader-open` class to the body tag of the page which will disable or enable the scrolling of the page

```js
document.body.classList.add('loader-open');
document.body.classList.remove('loader-open');
```

Here, the data we will pass in between the opening and closing `Loader` tag will be available inside `props.children` so we can display a simple loading message or we can include an image to be shown as a loader.

Now, let’s use this component

Open `HomePage.js` file and after the `<JobsContext.Provider value={value}>` line add the Loader component

```js
<Loader show={isLoading}>Loading...</Loader>
```

Also, import the `Loader` at the top of the file

```js
import Loader from './Loader';
```

Now, you can remove the previously used below line

```js
{
  isLoading && <p className="loading">Loading...</p>;
}
```

Now, when we will stop loading more items?
Obviously when there are no more items.

The `Github Jobs API` returns an empty array `[]` in response when there are no more jobs which you can check by passing larger page number to the API [HERE](https://jobs.github.com/positions.json?page=10)

So to handle that open `HomePage.js` file and in `loadJobs` function, inside `.then` handler add following code

```js
if (response && response.jobs.length === 0) {
  setHideLoadMore(true);
} else {
  setHideLoadMore(false);
}
setIsLoading(false);
```

So your `loadJobs` function will look like this

```js
const loadJobs = (selection) => {
  const { dispatch } = props;
  const { description, location, full_time, page = 1 } = selection;
  let isLoadMore = false;
  if (selection.hasOwnProperty('page')) {
    isLoadMore = true;
  }
  dispatch(resetErrors());
  setIsLoading(true);
  dispatch(
    initiateGetJobs({ description, location, full_time, page }, isLoadMore)
  )
    .then((response) => {
      if (response && response.jobs.length === 0) {
        setHideLoadMore(true);
      } else {
        setHideLoadMore(false);
      }
      setIsLoading(false);
    })
    .catch(() => setIsLoading(false));
};
```

Add another state variable

```js
const [hideLoadMore, setHideLoadMore] = useState(false);
```

and for the load more button code, change

```js
{results.length > 0 && _.isEmpty(errors) && (
```

to

```js
{results.length > 0 && _.isEmpty(errors) && !hideLoadMore && (
```

So we just added an extra `!hideLoadMore` condition and now, if there are no more jobs coming from the response, we will hide the load more jobs button.

Now, if you check your application, you can see that the Load More Jobs button will not be displayed if there are no more jobs to load when we click on it. The beauty of including data to display in between the opening and closing `Loader` tag like this

```js
<Loader show={isLoading}>Loading...</Loader>
```

is that, we can include anything in between the tags even an image and that image will get displayed instead of the `Loading` text because we are using `props.children` to display inside the loader div using

```js
ReactDOM.createPortal(props.children, node);
```

You can find code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/added_loader_with_overlay)

## Adding Lazy Loading Images Functionality

As you are aware now when we are requesting from Jobs API, we are getting a list of `50` jobs initially and as we are showing the company logo on the list page, the browser has to download those `50` images which may take time so you might see the blank area sometimes before the image is fully loaded.

![blank area](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/bbe253df967b28af6f8ed9797712c1804a870f14/blank_area.gif)

Also if you are browsing the application on a mobile device and you are using a slow network connection, it may take more time to download the images and those much `MB` of unnecessary images browser may download even if you are not scrolling the page to see other jobs listing which is not good user experience.

If you check the current functionality until this point when we click the Search button without entering any value, For me there are a total of `99` requests which took around `2MB` of data.

![request information](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/0672573457d7a369883f134e3f293c4b0e946ce6/request_info.png)

We can fix this by lazy loading the images. So until the user does not scroll to the job in the list, the image will not be downloaded which is more efficient.

So let’s start with it.

Create a new file `observer.js` inside `custom-hooks` folder with the following content

```js
import { useEffect, useState } from 'react';

const useObserver = (targetRef) => {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver((entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          if (!isVisible) {
            setIsVisible(true);
          }
          observer.unobserve(entry.target);
        } else {
          setIsVisible(false);
        }
      });
    });

    const current = targetRef.current;
    observer.observe(current);

    return () => {
      observer.unobserve(current);
    };
  }, [isVisible, targetRef]);

  return [isVisible];
};

export default useObserver;
```

In this file, we are using Intersection Observer API to identify which area of the page is currently displayed and only images in that area will be downloaded.

If you are not aware of the Intersection Observer, check out my previous article [HERE](https://levelup.gitconnected.com/what-is-so-special-about-intersection-observer-api-in-javascript-f2430a159fa7) which explains how to do lazy loading, sliding animation and play/pause video on a scroll in JavaScript in detail.

So in the `observer.js` file, we are taking a ref and adding that `ref` to be observed to the observer

```js
observer.observe(current);
```

If the image with added `ref` is displayed on screen then we are calling `setIsVisible(true);` and we are returning `theisVisible` value from this custom hook and based on `theisVisible` flag we can decide if we want to display the image or not.

So open `JobItem.js` file and add an import for the custom hook we created just now

```js
import useObserver from '../custom-hooks/observer';
```

Import `useRef` hook at the top of the file

```js
import React, { useRef } from 'react';
```

Create a `ref` which we can assign to the image

```js
const imageRef = useRef();
```

call the custom hook and get the `isVisible` value

```js
const [isVisible] = useObserver(imageRef);
```

change

```js
<div className="company-logo">
  <img src={company_logo} alt={company} width="100" height="100" />
</div>
```

to

```js
<div className="company-logo" ref={imageRef}>
  {isVisible && (
    <img src={company_logo} alt={company} width="100" height="100" />
  )}
</div>
```

Now, restart your `React` application by running `yarn run start` and check the lazy loading functionality.

![lazy loading](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/2808bea098d6958bea4c64b17b40fa9ac5031112/lazy_loading.gif)

As you can see initially only `5` requests are sent and only two logo images are downloaded and as you scroll the page, the next displayed images will be downloaded.

This is much better than the previous experience of downloading all the images at once. This will also load the page faster and save internet bandwidth.

You can find code until this point [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/tree/added_lazy_loading_images)

## Adding Default Loading Image

If you noticed, even if we are loading the images lazily, initially you will see blank area instead of the image until the image is fully loaded.

![blank area](https://gist.githubusercontent.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/c5e72536a35c20d6f580507fca89dddbb78d8df8/blank_area_.gif)

We can fix this by providing an alternative image and replace it with the original image once it's completely downloaded.

This way we can avoid the empty space and is a widely used way of not showing the empty image area.

Download the loader image from [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/blob/master/src/images/loading.png) and add it Inside the src/images folder

The website used for creating the image is [THIS](https://placeholder.com/).

You can specify the `width`, `height`, and `text` of the image you want.

The URL used to generate that loading image is this

```js
https://via.placeholder.com/100x100?text=Loading
```

Create a new file `Image.js` inside `components` folder with the following content

```js
import React from 'react';
import { useState } from 'react';
import loading from '../images/loading.png';

/* https://via.placeholder.com/100x100?text=Loading */

const Image = ({ src, alt, ...props }) => {
  const [isVisible, setIsVisible] = useState(false);
  const changeVisibility = () => {
    setIsVisible(true);
  };

  return (
    <React.Fragment>
      <img
        src={loading}
        alt={alt}
        width="100"
        height="100"
        style={{ display: isVisible ? 'none' : 'inline' }}
        {...props}
      />
      <img
        src={src}
        alt={alt}
        width="100"
        height="100"
        onLoad={changeVisibility}
        style={{ display: isVisible ? 'inline' : 'none' }}
        {...props}
      />
    </React.Fragment>
  );
};

export default Image;
```

In this file, we are initially displaying the loading image instead of the actual image.

The `img` tag has `onLoad` handler added which will be triggered when the image is completely loaded where we set the `isVisible` flag to true and once it's true we are displaying that image and hiding the previous loading image by using display CSS property.

Now open `JobItem.js` file and change

```js
{
  isVisible && (
    <img src={company_logo} alt={company} width="100" height="100" />
  );
}
```

to

```js
{
  isVisible && (
    <Image src={company_logo} alt={company} width="100" height="100" />
  );
}
```

Also, import `Image` component at the top of the file

```js
import Image from './Image';
```

Notice we have just changed `img` to `Image` and we are accessing the additional props in `Image` component as

```js
const Image = ({ src, alt, ...props }) => {
```

So except `src` and `alt` all other props like width, height will be stored in an array with the name props and then we are passing those props to the actual image by spreading the props array `{...props}` We can add the same functionality for the company logo on the details page.

Open `JobDetails.js` file and change

```js
<img src={company_logo} alt={company} className="company-logo" />
```

to

```js
<Image src={company_logo} alt={company} className="company-logo" />
```

Also, import the `Image` component at the top of the file

```js
import Image from './Image';
```

Now, restart your `React` application by running `yarn run start` and check it out

![loading image](https://gist.github.com/myogeshchavan97/e0be7fc4c838544e2d00afeb3a82ae10/raw/05391a0a57140b0844b1bcab731f41c22b59a113/loading_image.gif)

That's it about this article.

You can find complete Github source code for this application [HERE](https://github.com/myogeshchavan97/github-jobs-react-app/) and live demo [HERE](https://github-jobs-react-app.netlify.app/)


**Don't forget to subscribe to get my weekly newsletter with amazing tips, tricks and articles directly in your inbox [here](https://yogeshchavan.dev).**

[<img src="https://cdn.buymeacoffee.com/buttons/default-yellow.png" >](https://www.buymeacoffee.com/myogeshchavan97)