Hire+Plus! For Employees Here's how I built it (UI - Job)
Tags: react, webdev, programming, tutorial
Published: 2022-08-06 04:38:15

## Overview: All views and functionality related to the Job, all funcs called are coming from the jobReducer.

---

## Job Route Page
**inside `routes > job > job-page.tsx`**
imports that assist with components to render, and functions to call. On mount, I fetch all jobs. I'm handling search and filter states using `searchInput`, and `filteredData`. 

`searchItems` - filters jobs by search term and sets results to `filteredData`. If nothing was searched, `filteredData` is set to default `jobs`. 

```javascript
import { useEffect, useState } from 'react';
import BeatLoader from 'react-spinners/BeatLoader';
import { getPostedJobs } from '../../app/features/job/jobSlice';
import { JobData } from '../../app/features/job/jobTypes';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import Job from '../../components/job/job.component';

const JobsPage = () => {
	const dispatch = useAppDispatch();
	const { jobs, isLoading } = useAppSelector((state) => state.job);

	const [searchInput, setSearchInput] = useState<string>('');
	const [filteredData, setfilteredData] = useState<JobData[]>([]);

	useEffect(() => {
		dispatch(getPostedJobs());
	}, []);

	const searchItems = (value: string) => {
		setSearchInput(value);
		if (searchInput !== '') {
			const filtered = jobs.filter((item) => {
				return Object.values(item)
					.join('')
					.toLowerCase()
					.includes(searchInput.toLowerCase());
			});
			setfilteredData(filtered);
		} else {
			setfilteredData(jobs);
		}
	};
	return ({/* removed for simplicity */});
};

export default JobsPage;

```

### UI
In a nutshell, it renders all jobs with a search bar. Searching by job title, and or location will show filtered results in Job component.

```javascript
const JobsPage = () => {
{/* removed for simplicity */}
	return (
		<>
			{isLoading ? (
				<div className="text-center p-20">
					<BeatLoader color="#ffffff" />
				</div>
			) : (
				<>
					<div className="flex justify-center pt-20">
						<div className="mb-3 w-1/2">
							<div className="input-group relative flex items-stretch w-full mb-4">
								<input
									value={searchInput}
									onChange={(e) => searchItems(e.target.value)}
									type="search"
									className="form-control relative flex-auto min-w-0 block w-full px-5 py-3 text-base font-normal font-color secondary-bg-color bg-clip-padding border border-solid border-gray-300 rounded-full transition ease-in-out m-0 focus:text-slate-200 focus:secondary-bg-color focus:border-indigo-700 focus:outline-none"
									placeholder="Search for a job..."
									aria-label="Search"
									aria-describedby="button-addon2"
								/>
							</div>
						</div>
					</div>
					<section className="text-gray-600 body-font overflow-hidden">
						<div className="container px-5 py-24 mx-auto">
							<div className="-my-8 divide-y-2 divide-gray-700">
								{searchInput.length
									? filteredData.map((job, index) => {
											return <Job job={job} key={index} />;
									  })
									: jobs.map((job, index) => {
											return <Job job={job} key={index} />;
									  })}
							</div>
						</div>
					</section>
				</>
			)}
		</>
	);
};

export default JobsPage;

```

### Screenshots
1. when it's not filtered
2. when it's filtered

![Jobs unfiltered results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkqq66ts67zuylpwxu8i.png)

![Job filtered results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilyh4513lpx14udln11w.png)

**inside `routes > job > job-detail.tsx`**
imports that assist with functions to call. On mount, I fetch a single job by `id` I get from `useParams`. I parse the result and set it to local state `jobData`.

```javascript
import { useEffect, useState } from 'react';
import { useParams } from 'react-router';
import BeatLoader from 'react-spinners/BeatLoader';
import { getPostedJobById } from '../../app/features/job/jobSlice';
import { JobData } from '../../app/features/job/jobTypes';
import { useAppDispatch, useAppSelector } from '../../app/hooks';

const JobDetail = () => {
	const { id } = useParams();
	const [jobData, setjobData] = useState<JobData>({} as JobData);
	const { isLoading } = useAppSelector((state) => state.job);
	const dispatch = useAppDispatch();

	useEffect(() => {
		dispatch(getPostedJobById(id))
			.unwrap()
			.then((val) => {
				setjobData(JSON.parse(val));
			});
	}, [dispatch, id]);

	return ( {/* removed for simplicity */} );
};

export default JobDetail;

```

### UI
In a nutshell, it renders the details of a job. 

```javascript
const JobDetail = () => {
	{/* removed for simplicity */}
	return (
		<>
			{isLoading ? (
				<BeatLoader />
			) : (
				<>
					<section style={{ backgroundColor: '#252731' }}>
						<div className="md:px-12 lg:px-24 max-w-7xl relative items-center w-full px-5 py-20 mx-auto">
							<div className="mx-auto flex flex-col w-full max-w-lg text-center">
								<p className="mb-5 font-medium text-3xl text-white">
									{jobData.position}
								</p>
								<div>
									<span className="font-color">
										{jobData.location} - {jobData.salary}
									</span>
								</div>
							</div>
						</div>
					</section>
					<section>
						<div className="divide-y divide-gray-700">
							<section className="text-gray-600 body-font mt-2">
								<div className="container px-5 py-20 mx-auto">
									<div className="flex flex-col w-full mx-auto">
										<div className="w-full mx-auto">
											<h2 className="xs:text-3xl text-2xl my-5 font-bold">
												Job Description
											</h2>
											<div>
												<p className="font-color">{jobData.description}</p>
											</div>
										</div>
									</div>
								</div>
							</section>
							<section className="text-gray-600 body-font mt-2">
								<div className="container px-5 py-20 mx-auto">
									<div className="flex flex-col w-full mx-auto">
										<div className="w-full mx-auto">
											<h2 className="xs:text-3xl text-2xl my-5 font-bold">
												Job-Type
											</h2>
											<div>
												<p className="font-color">{jobData.jobType}</p>
											</div>
										</div>
									</div>
								</div>
							</section>
						</div>
					</section>
				</>
			)}
		</>
	);
};

export default JobDetail;

```

### Screenshot

![Job detail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mi7gu20fkocc88umf4cc.png)

---

## Job Component
**inside `components > job > job.component.tsx`**
I created a helper func `truncateString` for text that's too long. If text reaches max length it shows `...` 

I get the job data from props, since I'm using typescript I defined what the data type of the prop should be, in my case `JobData`.

```javascript
import React from 'react';
import { Link } from 'react-router-dom';
import { JobData } from '../../app/features/job/jobTypes';
import { truncateString } from '../../utils/truncateString';

interface JobProps {
     job: JobData;
}
```

### UI
Job card with links to view details, apply for the job, and or view the company that posted it.  

```javascript
const Job: React.FC<JobProps> = ({ job }) => {
	return (
		<div className="py-8 flex flex-wrap md:flex-nowrap">
			<div className="md:w-64 md:mb-0 mb-6 flex-shrink-0 flex flex-col">
				<span className="font-semibold title-font text-indigo-500">
					{job.companyName.toUpperCase()}
				</span>
				<span className="mt-1 font-color text-sm">{job.datePosted}</span>
			</div>
			<div className="md:flex-grow">
				<h2 className="text-2xl font-medium text-white title-font mb-2">
					{job.position}{' '}
					<span className="text-indigo-500 text-sm">
						({job.location}) - ({job.jobType})
					</span>
				</h2>
				<p className="leading-relaxed font-color max-w-3xl">
					{truncateString(job.description, 250)}
				</p>
				<a
					href={job.applyUrl}
					className="text-indigo-500 inline-flex items-center mt-4 mr-4"
				>
					APPLY NOW
					<svg
						className="w-4 h-4 ml-2"
						viewBox="0 0 24 24"
						stroke="currentColor"
						strokeWidth="2"
						fill="none"
						strokeLinecap="round"
						strokeLinejoin="round"
					>
						<path d="M5 12h14"></path>
						<path d="M12 5l7 7-7 7"></path>
					</svg>
				</a>
				<Link
					to={`job/${job.id}`}
					className="text-indigo-500 inline-flex items-center mt-4 mr-3"
				>
					VIEW JOB
					<svg
						className="w-4 h-4 ml-2"
						viewBox="0 0 24 24"
						stroke="currentColor"
						strokeWidth="2"
						fill="none"
						strokeLinecap="round"
						strokeLinejoin="round"
					>
						<path d="M5 12h14"></path>
						<path d="M12 5l7 7-7 7"></path>
					</svg>
				</Link>
				<Link
					to={`company/${job.id}`}
					className="text-indigo-500 inline-flex items-center mt-4"
				>
					VIEW COMPANY
					<svg
						className="w-4 h-4 ml-2"
						viewBox="0 0 24 24"
						stroke="currentColor"
						strokeWidth="2"
						fill="none"
						strokeLinecap="round"
						strokeLinejoin="round"
					>
						<path d="M5 12h14"></path>
						<path d="M12 5l7 7-7 7"></path>
					</svg>
				</Link>
			</div>
		</div>
	);
};

export default Job;

```

That's all for the UI/Job portion of the project, stay tuned!